Skip to content

Commit

Permalink
✨ LaspyReader for reading LAS/LAZ point cloud files
Browse files Browse the repository at this point in the history
An iterable-style DataPipe for reading LAS/LAZ point cloud files! Uses laspy (with either lazrs or laszip backend) for the I/O. Included a doctest that ensures a LAZ file from OpenTopography can be read. Added a new section in the API docs too.
  • Loading branch information
weiji14 committed Jan 30, 2024
1 parent f9795e5 commit 4735bea
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 0 deletions.
3 changes: 3 additions & 0 deletions docs/_config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ sphinx:
geopandas:
- 'https://geopandas.org/en/latest/'
- null
laspy:
- 'https://laspy.readthedocs.io/en/latest/'
- null
mmdetection:
- 'https://mmdetection.readthedocs.io/zh_CN/latest/'
- null
Expand Down
9 changes: 9 additions & 0 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,15 @@
:show-inheritance:
```

### Laspy

```{eval-rst}
.. automodule:: zen3geo.datapipes.laspy
.. autoclass:: zen3geo.datapipes.LaspyReader
.. autoclass:: zen3geo.datapipes.laspy.LaspyReaderIterDataPipe
:show-inheritance:
```

### Pyogrio

```{eval-rst}
Expand Down
1 change: 1 addition & 0 deletions zen3geo/datapipes/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from zen3geo.datapipes.geopandas import (
GeoPandasRectangleClipperIterDataPipe as GeoPandasRectangleClipper,
)
from zen3geo.datapipes.laspy import LaspyReaderIterDataPipe as LaspyReader
from zen3geo.datapipes.pyogrio import PyogrioReaderIterDataPipe as PyogrioReader
from zen3geo.datapipes.pystac import PySTACItemReaderIterDataPipe as PySTACItemReader
from zen3geo.datapipes.pystac_client import (
Expand Down
90 changes: 90 additions & 0 deletions zen3geo/datapipes/laspy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
"""
DataPipes for :doc:`laspy <laspy:index>`.
"""
import io
from typing import Any, Dict, Iterator, Optional

try:
import laspy
except ImportError:
laspy = None
from torchdata.datapipes import functional_datapipe
from torchdata.datapipes.iter import IterDataPipe
from torchdata.datapipes.utils import StreamWrapper


@functional_datapipe("read_from_laspy")
class LaspyReaderIterDataPipe(IterDataPipe[StreamWrapper]):
"""
Takes LAS/LAZ files from local disk or an :py:class:`io.BytesIO` stream (as long as
they can be read by laspy) and yields :py:class:`laspy.lasdata.LasData` objects
(functional name: ``read_from_laspy``).
Parameters
----------
source_datapipe : IterDataPipe[str]
A DataPipe that contains filepaths or an :py:class:`io.BytesIO` stream to point
cloud files in LAS or LAZ format.
kwargs : Optional
Extra keyword arguments to pass to :py:func:`laspy.read`.
Yields
------
stream_obj : laspy.lasdata.LasData
A :py:class:`laspy.lasdata.LasData` object containing the point cloud data.
Raises
------
ModuleNotFoundError
If ``laspy`` is not installed. See
:doc:`install instructions for laspy <laspy:installation>`, (e.g. via
``pip install laspy[lazrs]``) before using this class.
Example
-------
>>> import pytest
>>> laspy = pytest.importorskip("laspy")
...
>>> from torchdata.datapipes.iter import IterableWrapper
>>> from zen3geo.datapipes import LaspyReader
...
>>> # Read in LAZ data using DataPipe
>>> file_url: str = "https://opentopography.s3.sdsc.edu/pc-bulk/NZ19_Wellington/CL2_BQ31_2019_1000_2138.laz"
>>> dp = IterableWrapper(iterable=[file_url])
>>> _, dp_stream = dp.read_from_http().unzip(sequence_length=2)
>>> dp_laspy = dp_stream.read_from_laspy()
...
>>> # Loop or iterate over the DataPipe stream
>>> it = iter(dp_laspy)
>>> lasdata = next(it)
>>> lasdata.header
<LasHeader(1.4, <PointFormat(6, 0 bytes of extra dims)>)>
>>> lasdata.xyz
array([[ 1.74977156e+06, 5.42749877e+06, -7.24000000e-01],
[ 1.74977152e+06, 5.42749846e+06, -7.08000000e-01],
[ 1.74977148e+06, 5.42749815e+06, -7.00000000e-01],
...,
[ 1.74976026e+06, 5.42756798e+06, -4.42000000e-01],
[ 1.74976029e+06, 5.42756829e+06, -4.17000000e-01],
[ 1.74976032e+06, 5.42756862e+06, -4.04000000e-01]])
"""

def __init__(
self, source_datapipe: IterDataPipe[str], **kwargs: Optional[Dict[str, Any]]
) -> None:
if laspy is None:
raise ModuleNotFoundError(
"Package `laspy` is required to be installed to use this datapipe. "
"Please use `pip install laspy` or "
"`conda install -c conda-forge laspy` to install the package"
)
self.source_datapipe: IterDataPipe[str] = source_datapipe
self.kwargs = kwargs

def __iter__(self) -> Iterator[StreamWrapper]:
for lazstream in self.source_datapipe:
yield StreamWrapper(laspy.read(source=lazstream, **self.kwargs))

def __len__(self) -> int:
return len(self.source_datapipe)

0 comments on commit 4735bea

Please sign in to comment.