Skip to content

Commit

Permalink
✅ Unit tests to read LAS/LAZ/COPC from disk or http
Browse files Browse the repository at this point in the history
Ensure that LAS files can be read from disk, and LAZ/COPC files can be read from io.Bytes streaming from HTTP. Also updated docstring in LaspyReaderIterDataPipe to mention io.BytesIO as possible input type, and that COPC is supported too.
  • Loading branch information
weiji14 committed Feb 1, 2024
1 parent 4735bea commit 23b0b28
Show file tree
Hide file tree
Showing 2 changed files with 121 additions and 3 deletions.
5 changes: 2 additions & 3 deletions zen3geo/datapipes/laspy.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
"""
DataPipes for :doc:`laspy <laspy:index>`.
"""
import io
from typing import Any, Dict, Iterator, Optional

try:
Expand All @@ -22,9 +21,9 @@ class LaspyReaderIterDataPipe(IterDataPipe[StreamWrapper]):
Parameters
----------
source_datapipe : IterDataPipe[str]
source_datapipe : IterDataPipe[str, io.BytesIO]
A DataPipe that contains filepaths or an :py:class:`io.BytesIO` stream to point
cloud files in LAS or LAZ format.
cloud data such as LAS, LAZ, COPC, etc.
kwargs : Optional
Extra keyword arguments to pass to :py:func:`laspy.read`.
Expand Down
119 changes: 119 additions & 0 deletions zen3geo/tests/test_datapipes_laspy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
"""
Tests for laspy datapipes.
"""
import tempfile
import urllib

import numpy as np
import numpy.testing as npt
import pytest
from torchdata.datapipes.iter import IterableWrapper

from zen3geo.datapipes import LaspyReader

laspy = pytest.importorskip("laspy")


# %%
def test_laspy_reader_las_local():
"""
Ensure that LaspyReader works to read in a LAS file (on disk) and outputs a
laspy.lasdata.LasData object.
"""
with tempfile.NamedTemporaryFile(suffix=".las") as tmpfile:
urllib.request.urlretrieve(
url="https://github.com/laz-rs/laz-rs/raw/0.8.3/tests/data/point-time-color.las",
filename=tmpfile.name,
)
dp = IterableWrapper(iterable=[tmpfile.name])

# Using class constructors
dp_laspy = LaspyReader(source_datapipe=dp)
# Using functional form (recommended)
dp_laspy = dp.read_from_laspy()

assert len(dp_laspy) == 1
it = iter(dp_laspy)
lasdata = next(it)

assert lasdata.header.version == laspy.header.Version(major=1, minor=2)
assert lasdata.header.point_format == laspy.point.PointFormat(point_format_id=3)
assert lasdata.points.array.shape == (1065,)
assert lasdata.xyz.shape == (1065, 3)
npt.assert_allclose(
actual=lasdata.xyz.mean(axis=0),
desired=[494494.6635117371, 4878134.831230047, 132.31299530516432],
)
npt.assert_allclose(actual=np.unique(lasdata.classification.array), desired=[1, 2])


def test_laspy_reader_laz_http():
"""
Ensure that LaspyReader works to read in a LAZ file (from a HTTP byte stream)
and outputs a laspy.lasdata.LasData object.
"""
file_url: str = "https://github.com/laz-rs/laz-rs/raw/0.8.3/tests/data/point-version-1-point-wise.laz"
dp = IterableWrapper(iterable=[file_url])
_, dp_stream = (
dp.read_from_http()
.read_from_stream()
.set_length(length=1)
.unzip(sequence_length=2)
)

# Using class constructors
dp_laspy = LaspyReader(source_datapipe=dp_stream)
# Using functional form (recommended)
dp_laspy = dp_stream.read_from_laspy()

assert len(dp_laspy) == 1
it = iter(dp_laspy)
lasdata = next(it)

assert lasdata.header.version == laspy.header.Version(major=1, minor=0)
assert lasdata.header.point_format == laspy.point.PointFormat(point_format_id=0)
assert lasdata.points.array.shape == (11781,)
assert lasdata.xyz.shape == (11781, 3)
npt.assert_allclose(
actual=lasdata.xyz.mean(axis=0),
desired=[2483799.026934895, 366405.56612511666, 1511.9428214922332],
)
npt.assert_allclose(
actual=np.unique(lasdata.classification), desired=[1, 2, 8, 9, 12, 15]
)


def test_laspy_reader_copc_http():
"""
Ensure that LaspyReader works to read in a COPC file (from a HTTP byte stream) and
outputs a laspy.lasdata.LasData object.
"""
file_url: str = (
"https://github.com/laspy/laspy/raw/2.5.3/tests/data/simple_with_page.copc.laz"
)
dp = IterableWrapper(iterable=[file_url])
_, dp_stream = (
dp.read_from_http()
.read_from_stream()
.set_length(length=1)
.unzip(sequence_length=2)
)

# Using class constructors
dp_laspy = LaspyReader(source_datapipe=dp_stream)
# Using functional form (recommended)
dp_laspy = dp_stream.read_from_laspy()

assert len(dp_laspy) == 1
it = iter(dp_laspy)
lasdata = next(it)

assert lasdata.header.version == laspy.header.Version(major=1, minor=4)
assert lasdata.header.point_format == laspy.point.PointFormat(point_format_id=7)
assert lasdata.points.array.shape == (1065,)
assert lasdata.xyz.shape == (1065, 3)
npt.assert_allclose(
actual=lasdata.xyz.mean(axis=0),
desired=[637296.7351830985, 851249.5384882629, 434.0978403755869],
)
npt.assert_allclose(actual=np.unique(lasdata.classification), desired=[1, 2])

0 comments on commit 23b0b28

Please sign in to comment.