Stream helpers for spatial formats.
What actually streams today
- EXCP (
:spatial_binary), GSPL (:gsplat), and PLY withsource: :file(or:autopath detection) read the header, then one record/vertex at a time from disk - O(header + one record) memory. Binary PLY uses a fixed stride; ASCII PLY reads lines. - In-memory binaries still materialize through
decode/2, then yield. A future Rust backend may memory-map large binaries. encode_to_file/3with an explicit:schemastreams EXCP/GSPL to disk (placeholder header, then seek-back count patch). Without:schema, encoding still collects in memory first.
Prefer source: :file when the argument is a filesystem path, or
source: :binary when it is an encoded payload (:binary is the default).
With explicit :auto, a short path-like binary that names a regular file is
opened; otherwise the argument is treated as payload bytes. Prefer explicit
:file / :binary for untrusted input - :auto can open local paths.
Path-like means under 4 KiB, no ply/EXCP/GSPL magic prefix, and
contains / or \ or ends with .ply/.excp/.gspl/.bin, and
File.regular?/1 is true.
Summary
Functions
Returns an enumerable over points or Gaussians decoded from a path or binary.
Encodes an enumerable of points or Gaussians after collecting it in memory.
Encodes spatial data and writes the binary to path.
Functions
@spec decode( Path.t() | binary(), keyword() ) :: Enumerable.t()
Returns an enumerable over points or Gaussians decoded from a path or binary.
EXCP/GSPL/PLY file sources stream incrementally; in-memory binaries still materialize before emitting elements.
Arguments
source(Path.t() | binary()) - a path or complete encoded payload. Paths and payloads are both binaries, so:sourcecontrols resolution.opts(keyword()) - requires:format::ply,:spatial_binary, or:gsplat.:sourcemay be:binary(default),:file, or:auto.:file, or:binary; PLY also accepts:as.
Returns
An Enumerable.t() yielding %Point{} for PLY/EXCP point clouds or
%Gaussian{} for PLY/GSPL Gaussian clouds. A failure is delayed until
enumeration and represented by exactly one
{:error, %ExCodecs.Error{}} element:
reason: :invalid_options-:formatis absent.reason: :unsupported_codec-:formatis unknown.reason: :io_error- a selected file cannot be opened or read.reason: :invalid_data- the payload is malformed, unsupported, or truncated.
Raises / exceptions
A missing format does not raise. Keyword.fetch/2 and decoder option access
raise FunctionClauseError when opts is not a proper keyword list. PLY
source dispatch raises FunctionClauseError for a non-binary source.
EXCP/GSPL source resolution has no clause for non-binaries and may return an
invalid value that causes CaseClauseError; an unsupported :source value
also raises CaseClauseError. Decoder exceptions may occur during
enumeration.
Examples
iex> alias ExCodecs.Spatial.{Point, PointCloud}
iex> {:ok, bin} = ExCodecs.Spatial.encode(PointCloud.new([Point.new(0.0, 0.0, 0.0)]), format: :ply)
iex> [%Point{}] = ExCodecs.Spatial.Stream.decode(bin, format: :ply) |> Enum.to_list()
iex> true
true
@spec encode( Enumerable.t(), keyword() ) :: {:ok, binary()} | {:error, ExCodecs.Error.t()}
Encodes an enumerable of points or Gaussians after collecting it in memory.
A non-empty enumerable is classified by its first element. Empty input is
encoded as an empty %PointCloud{} for PLY/EXCP and as an empty
%GaussianCloud{} for GSPL.
Arguments
enumerable(Enumerable.t()) -%Point{}or%Gaussian{}elements. The list should be homogeneous and contain valid struct shapes.opts(keyword()) - requires:format::ply,:spatial_binary, or:gsplat. Remaining keys are forwarded toExCodecs.Spatial.encode/2.
Returns
{:ok, payload}wherepayloadis abinary().{:error, %ExCodecs.Error{reason: :invalid_options}}if:formatis absent.{:error, %ExCodecs.Error{reason: :unsupported_codec}}for an unknown format (including empty input).{:error, %ExCodecs.Error{reason: :invalid_data}}when the first item is an error tuple or is not a%Point{}/%Gaussian{}, when cloud and format are incompatible, or when codec validation fails.
Raises / exceptions
Enum.to_list/1 raises Protocol.UndefinedError for a non-enumerable and
propagates exceptions raised by enumeration. Keyword access raises
FunctionClauseError for a non-keyword opts. Manually malformed point or
Gaussian structs can raise codec shape/numeric exceptions; PLY converts its
encoding exceptions to :invalid_data.
Examples
iex> alias ExCodecs.Spatial.Point
iex> {:ok, bin} = ExCodecs.Spatial.Stream.encode([Point.new(1.0, 0.0, 0.0)], format: :ply)
iex> is_binary(bin)
true
@spec encode_to_file( Enumerable.t() | ExCodecs.Spatial.PointCloud.t() | ExCodecs.Spatial.GaussianCloud.t(), Path.t(), keyword() ) :: :ok | {:error, ExCodecs.Error.t()}
Encodes spatial data and writes the binary to path.
When :schema is present and :format is :spatial_binary or :gsplat,
points/Gaussians are written incrementally via
Binary.stream_encode_to_file/3 / Gsplat.stream_encode_to_file/3
(placeholder header + seek-back count). Otherwise the payload is encoded in
memory and written with File.write/2.
Arguments
data(PointCloud.t() | GaussianCloud.t() | Enumerable.t()) - a cloud, or an enumerable of%Point{}/%Gaussian{}values.path(Path.t()) - destination path; parent directories must already exist.opts(keyword()) - the options forExCodecs.Spatial.encode/2, with:formatrequired for enumerable input and defaulting to:plyfor an already-built cloud. For streaming EXCP/GSPL writes, pass:schema(see codec docs).
Returns
:okafter the complete payload is written.- any
:invalid_options,:unsupported_codec, or:invalid_dataerror returned by encoding. {:error, %ExCodecs.Error{reason: :io_error, details: reason}}when a file/POSIX error occurs (e.g.:enoent,:eacces,:enospc).
Raises / exceptions
Normal path/POSIX failures are returned. Invalid path terms or non-path
binaries can raise FunctionClauseError or ArgumentError in the path/file
APIs. Encoding also has the enumerable, keyword, and malformed-struct
exceptions documented by encode/2.
Examples
iex> alias ExCodecs.Spatial.{Point, PointCloud}
iex> path = Path.join(System.tmp_dir!(), "ex_codecs_doc_3202.ply")
iex> :ok = ExCodecs.Spatial.Stream.encode_to_file(PointCloud.new([Point.new(0, 0, 0)]), path, format: :ply)
iex> {:ok, "ply" <> _} = File.read(path)
iex> File.rm!(path)
:ok