Skip to content

Commit

Permalink
Fix chunking of LazPerf writer.
Browse files Browse the repository at this point in the history
Add LazPerf writer test.
Add documentation regarding compressor/decompressor selection.
  • Loading branch information
abellgithub committed Sep 17, 2015
1 parent c5037fb commit f2bf939
Show file tree
Hide file tree
Showing 7 changed files with 102 additions and 21 deletions.
6 changes: 6 additions & 0 deletions doc/stages/readers.las.rst
Expand Up @@ -47,3 +47,9 @@ extra_dims

.. _LAS format: http://asprs.org/Committee-General/LASer-LAS-File-Format-Exchange-Activities.html

compression
May be set to "lazperf" or "laszip" to choose either the LazPerf decompressor
or the LasZip decompressor for LAZ files. PDAL must have been build with
support for the decompressor being requested. The LazPerf decompressor
doesn't support version 1 LAZ files or version 1.4 of LAS.
[Default: "laszip"]
7 changes: 4 additions & 3 deletions doc/stages/writers.las.rst
Expand Up @@ -112,9 +112,10 @@ project_id
UID reserved for the user [Default: Nil UID]

compression
Set to true to apply compression to the output, creating a LAZ file instead
of an LAS file. Requires PDAL to have been built with compression support
by linking with LASzip. [Default: false]
Set to "lazperf" or "laszip" to apply compression to the output, creating
a LAZ file instead of an LAS file. "lazperf" selects the LazPerf compressor
and "laszip" (or "true") selects the LasZip compressor. PDAL must have
been built with support for the requested compressor. [Default: "none"]

scale_x, scale_y, scale_z
Scale to be divided from the X, Y and Z nominal values, respectively, after
Expand Down
25 changes: 16 additions & 9 deletions include/pdal/Compression.hpp
Expand Up @@ -207,10 +207,17 @@ class LazPerfCompressor

class LazPerfVlrCompressor
{
typedef laszip::io::__ofstream_wrapper<std::ostream> OutputStream;
typedef laszip::encoders::arithmetic<OutputStream> Encoder;
typedef laszip::formats::dynamic_compressor Compressor;
typedef laszip::factory::record_schema Schema;

public:
LazPerfVlrCompressor(std::ostream& stream) :
m_stream(stream), m_outputStream(stream), m_chunksize(50000),
m_chunkPointsWritten(0), m_chunkInfoPos(0), m_chunkOffset(0)
LazPerfVlrCompressor(std::ostream& stream, const Schema& schema,
uint32_t chunksize) :
m_stream(stream), m_outputStream(stream), m_schema(schema),
m_chunksize(chunksize), m_chunkPointsWritten(0), m_chunkInfoPos(0),
m_chunkOffset(0)
{}

~LazPerfVlrCompressor()
Expand Down Expand Up @@ -247,6 +254,8 @@ class LazPerfVlrCompressor
m_encoder->done();
m_encoder.reset();

newChunk();

// Save our current position. Go to the location where we need
// to write the chunk table offset at the beginning of the point data.
std::streampos chunkTablePos = m_stream.tellp();
Expand All @@ -265,6 +274,7 @@ class LazPerfVlrCompressor
OutputStream outputStream(m_stream);
Encoder encoder(outputStream);
laszip::compressors::integer compressor(32, 2);
compressor.init();

uint32_t predictor = 0;
for (uint32_t offset : m_chunkTable)
Expand All @@ -279,7 +289,8 @@ class LazPerfVlrCompressor
private:
void resetCompressor()
{
m_encoder->done();
if (m_encoder)
m_encoder->done();
m_encoder.reset(new Encoder(m_outputStream));
m_compressor = laszip::factory::build_compressor(*m_encoder, m_schema);
}
Expand All @@ -292,11 +303,6 @@ class LazPerfVlrCompressor
m_chunkPointsWritten = 0;
}

typedef laszip::io::__ofstream_wrapper<std::ostream> OutputStream;
typedef laszip::encoders::arithmetic<OutputStream> Encoder;
typedef laszip::formats::dynamic_compressor Compressor;
typedef laszip::factory::record_schema Schema;

std::ostream& m_stream;
OutputStream m_outputStream;
std::unique_ptr<Encoder> m_encoder;
Expand Down Expand Up @@ -396,6 +402,7 @@ class LazPerfVlrDecompressor

#else

typedef char LazPerfVlrCompressor;
typedef char LazPerfVlrDecompressor;

#endif // PDAL_HAVE_LAZPERF
Expand Down
2 changes: 0 additions & 2 deletions io/las/LasReader.hpp
Expand Up @@ -103,9 +103,7 @@ class PDAL_DLL LasReader : public pdal::Reader
LasHeader m_lasHeader;
std::unique_ptr<ZipPoint> m_zipPoint;
std::unique_ptr<LASunzipper> m_unzipper;
#ifdef PDAL_HAVE_LAZPERF
std::unique_ptr<LazPerfVlrDecompressor> m_decompressor;
#endif
point_count_t m_index;
std::istream* m_istream;
VlrList m_vlrs;
Expand Down
6 changes: 3 additions & 3 deletions io/las/LasWriter.cpp
Expand Up @@ -130,7 +130,6 @@ void LasWriter::processOptions(const Options& options)
std::string compression =
options.getValueOrDefault<std::string>("compression");
compression = Utils::toupper(compression);
std::cerr << "Compression = " << compression << "!\n";
if (compression == "LASZIP" || compression == "TRUE")
m_compression = LasCompression::LasZip;
else if (compression == "LAZPERF")
Expand Down Expand Up @@ -357,7 +356,7 @@ void LasWriter::prepOutput(std::ostream *outStream)
if (m_lasHeader.versionEquals(1, 0))
out << (uint16_t)0xCCDD;
m_lasHeader.setPointOffset((uint32_t)m_ostream->tellp());
if (m_lasHeader.compressed())
if (m_compression == LasCompression::LasZip)
openCompression();

m_error.setLog(log());
Expand Down Expand Up @@ -653,7 +652,8 @@ void LasWriter::readyLazPerfCompression()
zipvlr.extract((char *)data.data());
addVlr(LASZIP_USER_ID, LASZIP_RECORD_ID, "http://laszip.org", data);

m_compressor.reset(new LazPerfVlrCompressor(*m_ostream));
m_compressor.reset(new LazPerfVlrCompressor(*m_ostream, schema,
zipvlr.chunk_size));
#endif
}

Expand Down
2 changes: 2 additions & 0 deletions test/unit/io/las/LasReaderTest.cpp
Expand Up @@ -393,6 +393,7 @@ TEST(LasReaderTest, callback)
EXPECT_EQ(count, (point_count_t)1065);
}

#ifdef PDAL_HAVE_LAZPERF
// LAZ files are normally written in chunks of 50,000, so a file of size
// 110,000 ensures we read some whole chunks and a partial.
TEST(LasReaderTest, lazperf)
Expand Down Expand Up @@ -437,6 +438,7 @@ TEST(LasReaderTest, lazperf)
EXPECT_EQ(memcmp(buf1.get(), buf2.get(), pointSize), 0);
}
}
#endif


// The header of 1.2-with-color-clipped says that it has 1065 points,
Expand Down
75 changes: 71 additions & 4 deletions test/unit/io/las/LasWriterTest.cpp
Expand Up @@ -207,12 +207,13 @@ TEST(LasWriterTest, extra_dims)
Options reader2Ops;
reader2Ops.add("filename", Support::temppath("simple.las"));
reader2Ops.add("extra_dims", "R1 =int32, B1= int16 ,G1=int32_t");
std::shared_ptr<LasReader> reader2(new LasReader);
reader2->setOptions(reader2Ops);

LasReader reader2;
reader2.setOptions(reader2Ops);

PointTable readTable;
reader2->prepare(readTable);
viewSet = reader2->execute(readTable);
reader2.prepare(readTable);
viewSet = reader2.execute(readTable);
pb = *viewSet.begin();
Dimension::Id::Enum r1 = readTable.layout()->findDim("R1");
EXPECT_TRUE(r1 != Dimension::Id::Unknown);
Expand Down Expand Up @@ -461,6 +462,72 @@ TEST(LasWriterTest, flex2)
EXPECT_EQ(r.preview().m_pointCount, 1065u);
}

#ifdef PDAL_HAVE_LAZPERF
// LAZ files are normally written in chunks of 50,000, so a file of size
// 110,000 ensures we read some whole chunks and a partial.
TEST(LasWriterTest, lazperf)
{
Options readerOps;
readerOps.add("filename", Support::datapath("las/autzen_trim.las"));

LasReader lazReader;
lazReader.setOptions(readerOps);

Options writerOps;
writerOps.add("filename", Support::temppath("temp.laz"));
writerOps.add("compression", "lazperf");

LasWriter lazWriter;
lazWriter.setOptions(writerOps);
lazWriter.setInput(lazReader);

PointTable t;
lazWriter.prepare(t);
lazWriter.execute(t);

// Now test the points were properly written. Use laszip.
Options ops1;
ops1.add("filename", Support::temppath("temp.laz"));

LasReader r1;
r1.setOptions(ops1);

PointTable t1;
r1.prepare(t1);
PointViewSet set1 = r1.execute(t1);
PointViewPtr view1 = *set1.begin();

Options ops2;
ops2.add("filename", Support::datapath("las/autzen_trim.las"));

LasReader r2;
r2.setOptions(ops2);

PointTable t2;
r2.prepare(t2);
PointViewSet set2 = r2.execute(t2);
PointViewPtr view2 = *set2.begin();

EXPECT_EQ(view1->size(), view2->size());
EXPECT_EQ(view1->size(), (point_count_t)110000);

DimTypeList dims = view1->dimTypes();
size_t pointSize = view1->pointSize();
EXPECT_EQ(view1->pointSize(), view2->pointSize());

// Validate some point data.
std::unique_ptr<char> buf1(new char[pointSize]);
std::unique_ptr<char> buf2(new char[pointSize]);
for (PointId i = 0; i < view1->pointSize(); i += 100)
{
view1->getPackedPoint(dims, i, buf1.get());
view2->getPackedPoint(dims, i, buf2.get());
EXPECT_EQ(memcmp(buf1.get(), buf2.get(), pointSize), 0);
}
}
#endif


/**
namespace
{
Expand Down

0 comments on commit f2bf939

Please sign in to comment.