Metadata-Version: 2.4
Name: voyant-api
Version: 0.7.1
Classifier: Programming Language :: Rust
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: License :: Other/Proprietary License
Classifier: Intended Audience :: Developers
Classifier: Topic :: Scientific/Engineering
Requires-Dist: numpy>=2.0
Requires-Dist: pandas>=2.0
Requires-Dist: pypcd4>=1.4
Requires-Dist: pytest>=8.0 ; extra == 'dev'
Requires-Dist: mypy>=1.15 ; extra == 'dev'
Requires-Dist: pyo3-stubgen>=0.3 ; extra == 'dev'
Provides-Extra: dev
License-File: LICENSE
Summary: Python bindings for Voyant Photonics, Inc. sensors
Author-email: "Voyant Photonics, Inc." <support@voyantphotonics.com>
Requires-Python: >=3.9
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
Project-URL: Documentation, https://voyant-photonics.github.io/
Project-URL: Examples, https://github.com/Voyant-Photonics/voyant-sdk
Project-URL: Homepage, https://voyant-photonics.github.io/
Project-URL: Issues, https://github.com/Voyant-Photonics/voyant-sdk/issues

# Voyant API - Python Bindings

Python bindings for Voyant Photonics LiDAR sensors, providing high-performance access to point cloud data.

## Installation

```bash
pip install voyant-api
```

## Sensor compatibility

| Sensor | Client |
|--------|--------|
| Carbon | `CarbonClient` + `CarbonConfig` |
| Meadowlark | `VoyantClient` *(deprecated — see below)* |

## Quick Start

### Receiving live data (Carbon)

```python
import time
from voyant_api import CarbonClient, CarbonConfig, init_voyant_logging

init_voyant_logging()

config = CarbonConfig()
config.set_bind_addr("0.0.0.0:5678")
config.set_group_addr("224.0.0.0")
config.set_interface_addr("192.168.1.100")

client = CarbonClient(config)
client.start()

# Press Ctrl+C to stop
while client.is_running():
    frame = client.try_receive_frame()
    if frame is not None:
        print(frame)

        # Get point cloud as numpy array (N x 4: x, y, z, radial_vel)
        xyzv = frame.xyzv()
        print(f"Points shape: {xyzv.shape}")
    else:
        time.sleep(0.001)
```

Config can also be loaded from a JSON file — see [CarbonConfig JSON format](#carbonconfig-json-format) below.

### Recording data

```python
import time
from voyant_api import CarbonClient, CarbonConfig, VoyantRecorder, RecordStatus, init_voyant_logging

init_voyant_logging()

config = CarbonConfig()
config.set_bind_addr("0.0.0.0:5678")
config.set_group_addr("224.0.0.0")
config.set_interface_addr("192.168.1.100")

client = CarbonClient(config)
client.start()

with VoyantRecorder(
    output_path="my_recording.bin",
    timestamp_filename=True,
    max_total_frames=1000,  # Optional: stop after 1000 frames
) as recorder:
    while client.is_running():
        frame = client.try_receive_frame()
        if frame is not None:
            status = recorder.record_frame(frame)
            if status == RecordStatus.STOP:
                break
        else:
            time.sleep(0.001)
```

### Playing back recordings

```python
from voyant_api import VoyantPlayback, init_voyant_logging
from voyant_api.pandas_utils import frame_to_dataframe

init_voyant_logging()

with VoyantPlayback(filter_points=True) as playback:
    playback.open("my_recording.bin")

    for frame in playback:
        if frame is None:
            break

        print(frame)

        # Convert to pandas DataFrame
        df = frame_to_dataframe(frame)
        print(df.head())
```

### Converting recordings to PCD

```python
from voyant_api import VoyantPlayback, init_voyant_logging
from voyant_api.pcd_utils import save_frame_to_pcd, frame_to_extended_pcd

init_voyant_logging()

with VoyantPlayback(filter_points=True) as playback:
    playback.open("my_recording.bin")

    for frame in playback:
        if frame is None:
            break

        # Save directly to .pcd file
        save_frame_to_pcd(frame, f"frame_{frame.frame_index}.pcd")

        # Or get a PointCloud object for further processing
        pc = frame_to_extended_pcd(frame)
        pc.save(f"frame_{frame.frame_index}.pcd")
```

## API Overview

### CarbonClient

Receives live data from Carbon sensors. Construct with a `CarbonConfig` and call `start()` before polling for frames.

```python
config = CarbonConfig()
config.set_bind_addr("0.0.0.0:5678")
config.set_group_addr("224.0.0.0")
config.set_interface_addr("192.168.1.100")
config.set_range_max(50.0)
config.set_pfa(1e-4)

client = CarbonClient(config)
client.start()
```

### CarbonConfig

Configuration for the Carbon pipeline. Construct with defaults and setters, or load from JSON.

```python
# From defaults
config = CarbonConfig()
config.set_bind_addr("0.0.0.0:5678")

# From a JSON file
config = CarbonConfig.from_json("config.json")
```

#### CarbonConfig JSON format

All fields are optional and fall back to their defaults when omitted. To see all available fields and their current defaults, run:

```python
from voyant_api import CarbonConfig
print(CarbonConfig())
```

This prints the full nested config with all current defaults, for example:

```python
CarbonConfig { receiver: ReceiverConfig { multicast: MulticastReceiverConfig { bind_addr: "0.0.0.0:5678", group_addr: "224.0.0.0", interface_addr: "127.0.0.1" }, batch_size: 32, ... }, dsp: DspConfig { pfa: None, bandwidth_hz: 2000000000.0, elevation_fov_deg: 22.5, ... }, ... }
```

Any field shown in that output can be set in the JSON file. Fields showing `None` are unset and use sensor defaults.

### VoyantRecorder

Records frames to binary files with automatic splitting options.

```python
recorder = VoyantRecorder(
    output_path="recording.bin",
    timestamp_filename=True,         # Add timestamp to filename
    frames_per_file=None,            # Split after N frames
    duration_per_file=None,          # Split after N seconds
    size_per_file_mb=None,           # Split after N megabytes
    max_total_frames=None,           # Stop after N total frames
    max_total_duration=None,         # Stop after N total seconds
    max_total_size_mb=None,          # Stop after N total megabytes
)
```

### VoyantPlayback

Plays back recorded data with rate control.

```python
playback = VoyantPlayback(
    rate=1.0,              # Playback speed (None = as fast as possible)
    loopback=False,        # Loop continuously
    filter_points=True,    # Remove invalid points
)
playback.open("recording.bin")
```

### Frame data access

```python
# NumPy arrays
xyz   = frame.xyz()            # (N x 3): [x, y, z]
xyzv  = frame.xyzv()           # (N x 4): [x, y, z, radial_vel]
sph   = frame.spherical()      # (N x 3): [range, azimuth, elevation]

# Pandas DataFrames (via voyant_api.pandas_utils)
from voyant_api.pandas_utils import frame_to_dataframe, frame_to_extended_dataframe
df          = frame_to_dataframe(frame)           # 7 columns
df_extended = frame_to_extended_dataframe(frame)  # 11 columns

# PCD PointCloud objects (via voyant_api.pcd_utils)
from voyant_api.pcd_utils import frame_to_pcd, frame_to_extended_pcd, save_frame_to_pcd
pc = frame_to_pcd(frame)           # 7 fields
pc = frame_to_extended_pcd(frame)  # 11 fields
save_frame_to_pcd(frame, "out.pcd")

# Frame metadata
print(frame.frame_index)
print(frame.timestamp)
print(frame.n_points)
print(frame.n_valid_points)
```

---

## Migrating from VoyantClient

> **`VoyantClient` is deprecated as of v0.5.0 and will be removed in a future release. Carbon sensor users should migrate to `CarbonClient`.**

`VoyantClient` remains functional for Meadowlark sensors but will receive no new features or fixes.

The main differences:

| | `VoyantClient` (deprecated) | `CarbonClient` |
|---|---|---|
| Config | Constructor kwargs | `CarbonConfig` object |
| Lifecycle | No explicit start | `client.start()` required |
| Shutdown | — | `client.stop()` / `client.wait_for_shutdown()` |
| Timestamps | `use_msg_stamps` | `set_use_msg_timestamp()` — default `True` for Carbon |

Before:

```python
client = VoyantClient(
    bind_addr="0.0.0.0:4444",
    group_addr="224.0.0.0",
    interface_addr="192.168.1.100",
    filter_points=True,
    use_msg_stamps=True,
)

while True:
    frame = client.try_receive_frame()
    if frame is not None:
        process(frame)
```

After:

```python
config = CarbonConfig()
config.set_bind_addr("0.0.0.0:5678")
config.set_group_addr("224.0.0.0")
config.set_interface_addr("192.168.1.100")

client = CarbonClient(config)
client.start()

# Press Ctrl+C to stop
while client.is_running():
    frame = client.try_receive_frame()
    if frame is not None:
        process(frame)
    else:
        time.sleep(0.001)

client.stop()
```

---

## Features

- **High performance**: Rust-based implementation with zero-copy data access
- **NumPy integration**: Direct conversion to NumPy arrays via `frame.xyzv()`
- **Pandas support**: DataFrame conversion via `voyant_api.pandas_utils`
- **PCD support**: Point Cloud Data export via `voyant_api.pcd_utils`
- **Type hints**: Full type annotations for IDE support (`.pyi` stubs included)
- **Recording & playback**: Save and replay sensor data with timestamp preservation
- **Network streaming**: Multicast UDP support for live sensor data

## Complete examples

Full example scripts are available in the [voyant-sdk repository](https://github.com/Voyant-Photonics/voyant-sdk):

- `client_example.py` — Live data streaming with CarbonClient
- `recorder_example.py` — Recording with all options
- `playback_example.py` — Playback and processing
- `pcd_conversion_example.py` — Converting recordings to PCD files

## System requirements

- **Python**: 3.9 or later
- **Dependencies**: NumPy 2.0+, Pandas 2.0+, pypcd4 1.4+
- **Platforms**: Linux, Windows, macOS
- **Hardware**: Carbon sensors require v0.5.0+. Meadowlark sensors use the deprecated `VoyantClient`.

## Documentation

- **Full documentation**: https://voyant-photonics.github.io/
- **Examples repository**: https://github.com/Voyant-Photonics/voyant-sdk

## Support

- **Issues**: https://github.com/Voyant-Photonics/voyant-sdk/issues
- **Email**: support@voyantphotonics.com

## License

Proprietary — for use with Voyant Photonics hardware products only.

Copyright © 2025 Voyant Photonics, Inc. All rights reserved.

