Metadata-Version: 2.4
Name: ransaclpplus
Version: 0.1.0
Summary: Plane segmentation with RANSAC-LP+ for Open3D point clouds
Project-URL: Documentation, https://github.com/jmartinezot/lp4-plus/tree/main/docs
Project-URL: Homepage, https://github.com/jmartinezot/lp4-plus
Project-URL: Issues, https://github.com/jmartinezot/lp4-plus/issues
Project-URL: Source, https://github.com/jmartinezot/lp4-plus
Author-email: José María Martínez Otzeta <jmartinezot@gmail.com>
License-Expression: MIT
License-File: LICENSE
Keywords: 3d,cuda,open3d,plane-segmentation,point-cloud,ransac
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Scientific/Engineering
Requires-Python: >=3.9
Requires-Dist: open3d>=0.16.0
Requires-Dist: psutil>=5.9.0
Provides-Extra: docs
Requires-Dist: myst-parser>=2.0; extra == 'docs'
Requires-Dist: sphinx-automodapi; extra == 'docs'
Requires-Dist: sphinx>=7.0; extra == 'docs'
Provides-Extra: release
Requires-Dist: build>=1.2; extra == 'release'
Requires-Dist: twine>=5.1; extra == 'release'
Provides-Extra: test
Requires-Dist: pytest>=8.0; extra == 'test'
Description-Content-Type: text/markdown

# ransaclpplus

In this repository you can find the code related to the RANSACLPPLUS algorithm.

## Table of Contents

- [Installation](#installation)
- [Docker](#docker)
- [Publishing](#publishing)
- [Basic usage](#basic-usage)
- [Repository Structure](#repository-structure)
- [Reproducible Experiments](#reproducible-experiments)
- [Method](#method)
- [Current Research Status](#current-research-status)
- [CUDA backend](#cuda-backend)
- [CUDA benchmark](#cuda-benchmark)
- [Documentation with Sphinx](#documentation-with-sphinx)
- [Tests](#tests)
- [Visual Step-by-Step Test (Open3D)](#visual-step-by-step-test-open3d)
- [Run The Devcontainer From CLI (JSON-Equivalent)](#run-the-devcontainer-from-cli-json-equivalent)

## Installation

Install the CPU-portable package from PyPI:

```bash
python3 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install ransaclpplus
```

For repository development:

```bash
git clone https://github.com/jmartinezot/lp4-plus.git
cd lp4-plus
python -m pip install -e .
```

PyPI installations use CPU execution by default. To enable native CUDA on a
machine with the NVIDIA CUDA toolkit and CMake:

```bash
ransaclpplus-build-cuda --cuda-architectures "61;75;86;89;90"
```

See the
[installation guide](https://github.com/jmartinezot/lp4-plus/blob/main/docs/installation.md)
for complete installation, CUDA, devcontainer, and troubleshooting
instructions.

## Docker

Build and run the CPU image:

```bash
docker build -f docker/Dockerfile.cpu -t ransaclpplus:cpu .
docker run --rm -it ransaclpplus:cpu
```

Build and run the native CUDA image:

```bash
docker build -f docker/Dockerfile.cuda -t ransaclpplus:cuda .
docker run --rm -it --gpus=all ransaclpplus:cuda
```

See the
[Docker guide](https://github.com/jmartinezot/lp4-plus/blob/main/docker/README.md)
for architecture selection, dataset mounts, and Open3D display notes.

## Publishing

Package releases are published to PyPI from GitHub Releases using trusted
publishing. See the
[PyPI release guide](https://github.com/jmartinezot/lp4-plus/blob/main/docs/releasing.md)
for the one-time PyPI setup, local validation commands, and release procedure.

## Basic usage

An example of use:

```python
import ransaclpplus
import open3d as o3d

office_dataset = o3d.data.OfficePointClouds()
office_filename = office_dataset.paths[0]

ransaclpplus_iterations = 200
threshold = 0.02
seed = 42
percentage_chosen_lines = 0.2
percentage_chosen_planes = 0.05

pcd = o3d.io.read_point_cloud(office_filename)
plane_model, inliers = ransaclpplus.segment_plane(pcd, distance_threshold=threshold, num_iterations=ransaclpplus_iterations, 
                                                percentage_chosen_lines = percentage_chosen_lines,
                                                percentage_chosen_planes = percentage_chosen_planes, seed = seed)
number_inliers = len(inliers)
inlier_cloud = pcd.select_by_index(inliers)
inlier_cloud.paint_uniform_color([1, 0, 0])
o3d.visualization.draw_geometries([pcd, inlier_cloud], window_name="RANSACLPPLUS Inliers:  " + str(number_inliers))
```

You can use native C++ CUDA-accelerated line fitting by setting `use_cuda=True`:

```python
plane_model, inliers = ransaclpplus.segment_plane(pcd, distance_threshold=threshold, num_iterations=ransaclpplus_iterations, 
                                                percentage_chosen_lines = percentage_chosen_lines, use_cuda = True,
                                                percentage_chosen_planes = percentage_chosen_planes, seed = seed)
```

Adaptive line selection (optional) can replace fixed `percentage_chosen_lines`:

```python
plane_model, inliers = ransaclpplus.segment_plane(
    pcd,
    distance_threshold=threshold,
    num_iterations=ransaclpplus_iterations,
    percentage_chosen_lines=0.2,
    percentage_chosen_planes=percentage_chosen_planes,
    line_selection_mode="adaptive_poisson_mixture",
    adaptive_tau=0.5,
    adaptive_em_max_iter=100,
    adaptive_em_tol=1e-6,
    adaptive_min_lines=2,
    adaptive_allow_empty=False,
    seed=seed,
)
```

Compatibility-filtered pairing (optional) can be enabled for pair generation:

```python
plane_model, inliers = ransaclpplus.segment_plane(
    pcd,
    distance_threshold=threshold,
    num_iterations=ransaclpplus_iterations,
    percentage_chosen_lines=0.2,
    percentage_chosen_planes=percentage_chosen_planes,
    line_selection_mode="adaptive_poisson_mixture",
    adaptive_tau=0.5,
    pairing_filter_enabled=True,
    pairing_min_angle_deg=10.0,
    pairing_max_angle_deg=170.0,
    pairing_max_centroid_distance=0.6,
    seed=seed,
)
```

## Repository Structure

Experiment artifacts are organized with a canonical snapshot and a separate exploratory area:

- Canonical fixed-vs-adaptive Open3D experiment:
  - `results/open3d_fixed_vs_adaptive_alpha/`
- Canonical pairing-ablation snapshots:
  - `results/open3d_pairing_ablation_06/`
  - `results/open3d_pairing_ablation_pairing_max_centroid_20/`
  - `results/open3d_pairing_ablation_pairing_max_centroid_30/`
- Exploratory sweeps and follow-up tuning runs:
  - `results/exploratory/`
- S3DIS validation outputs:
  - `results/s3dis_pilot_validation/`
  - `results/s3dis_validation/`

Experiment code and run guide:

- Script: `experiments/run_open3d_fixed_vs_adaptive_alpha.py`
- Script (pairing ablation): `experiments/run_open3d_pairing_ablation.py`
- Script (angle-only ablation): `experiments/run_open3d_angle_only_ablation.py`
- Script (pairing-ablation post-analysis): `experiments/analyze_pairing_ablation_results.py`
- Script (S3DIS pilot validation): `experiments/run_s3dis_pilot_validation.py`
- Script (S3DIS full validation): `experiments/run_s3dis_validation.py`
- Helper (S3DIS dataset discovery): `experiments/s3dis_dataset.py`
- Usage guide: `experiments/README_open3d_fixed_vs_adaptive_alpha.md`
- Usage guide (angle-only ablation): `experiments/README_open3d_angle_only_ablation.md`
- Reproducibility overview: `docs/reproducibility.md`

## Reproducible Experiments

The canonical Open3D fixed-vs-adaptive-alpha snapshot is preserved in:

- `results/open3d_fixed_vs_adaptive_alpha/`

This directory includes:

- configuration (`experiment_config.json`)
- environment and git metadata (`environment.json`)
- exact dataset file manifest (`dataset_files.txt`)
- raw run-level outputs (`raw_runs.csv`)
- aggregated outputs (`aggregated_per_file.csv`, `aggregated_global.csv`)

Canonical pairing-ablation snapshots are stored separately by `pairing_max_centroid_distance`:

- `results/open3d_pairing_ablation_06/`
- `results/open3d_pairing_ablation_pairing_max_centroid_20/`
- `results/open3d_pairing_ablation_pairing_max_centroid_30/`

For each snapshot, derived post-analysis artifacts are generated in-place:

- `analysis/*.csv` and `analysis/*.md` summaries
- `plots/*.png` paper-oriented figures (runtime-vs-inliers, boxplots, pair-filter and rejection breakdowns, per-scene deltas)

Exploratory tuning runs remain intentionally separated in `results/exploratory/` and are not the primary paper snapshots.

Pairing-ablation post-analysis command (single snapshot):

```bash
python experiments/analyze_pairing_ablation_results.py \
  --results-dir results/open3d_pairing_ablation_06 \
  --plot-format png \
  --title-suffix "(pairing_max_centroid_distance=0.6)"
```

Manual multi-snapshot analysis sequence:

```bash
python experiments/analyze_pairing_ablation_results.py --results-dir results/open3d_pairing_ablation_06 --plot-format png --title-suffix "(pairing_max_centroid_distance=0.6)"
python experiments/analyze_pairing_ablation_results.py --results-dir results/open3d_pairing_ablation_pairing_max_centroid_20 --plot-format png --title-suffix "(pairing_max_centroid_distance=2.0)"
python experiments/analyze_pairing_ablation_results.py --results-dir results/open3d_pairing_ablation_pairing_max_centroid_30 --plot-format png --title-suffix "(pairing_max_centroid_distance=3.0)"
```

Completed follow-up snapshot:

- `results/open3d_angle_only_ablation/`

Angle-only rerun command:

```bash
python experiments/run_open3d_angle_only_ablation.py \
  --output-dir results/open3d_angle_only_ablation \
  --num-runs 10 \
  --n-line-iterations 250 \
  --threshold 0.03 \
  --beta 0.05 \
  --fixed-alpha 0.2 \
  --adaptive-tau 0.5 \
  --pairing-min-angle-deg 10 \
  --pairing-max-angle-deg 170 \
  --pairing-max-centroid-distance 3.0 \
  --use-cuda
```

S3DIS validation workflow:

- S3DIS is expected to be mounted read-only at `/datasets/S3DIS`.
- If `S3DIS_DATA_ROOT` is set, the S3DIS scripts use it by default; otherwise they fall back to `/datasets/S3DIS`.
- Run the pilot first on `Area_1`, review outputs, then launch the full validation later if approved.
- Search is performed on a reduced point set for efficiency:
  - optional voxelized cloud
  - optional deterministic search subset from that voxelized cloud
- Final support is re-evaluated on the full raw point cloud for reviewer-facing reporting.
- For S3DIS validation, the top-level reported final support metric is intended to be the raw-cloud support.

S3DIS pilot command:

```bash
python experiments/run_s3dis_pilot_validation.py \
  --output-dir results/s3dis_pilot_validation \
  --num-runs 10 \
  --n-line-iterations 250 \
  --threshold 0.03 \
  --beta 0.05 \
  --fixed-alpha 0.2 \
  --adaptive-tau 0.5 \
  --pairing-min-angle-deg 10 \
  --pairing-max-angle-deg 170 \
  --voxel-size 0.02 \
  --search-subset-size 30000 \
  --search-subset-seed 0 \
  --use-cuda
```

S3DIS full validation command:

```bash
python experiments/run_s3dis_validation.py \
  --output-dir results/s3dis_validation \
  --num-runs 10 \
  --n-line-iterations 250 \
  --threshold 0.03 \
  --beta 0.05 \
  --fixed-alpha 0.2 \
  --adaptive-tau 0.5 \
  --pairing-min-angle-deg 10 \
  --pairing-max-angle-deg 170 \
  --voxel-size 0.02 \
  --search-subset-size 30000 \
  --search-subset-seed 0 \
  --use-cuda
```

S3DIS post-analysis command:

```bash
python experiments/analyze_pairing_ablation_results.py \
  --results-dir results/s3dis_pilot_validation \
  --plot-format png \
  --title-suffix "(S3DIS pilot validation)"
```

Key conservative findings from the canonical snapshot:

- Adaptive alpha has higher mean best-plane inliers than fixed alpha (`128071.33` vs `127446.12`).
- Adaptive alpha has higher mean runtime than fixed alpha (`260.54 ms` vs `232.97 ms`).
- Per-file mean inlier comparison: adaptive better on `103/110`, equal on `3/110`, worse on `4/110`.
- Per-file mean runtime comparison: adaptive slower on `105/110`, faster on `5/110`.

All values above are derived from:

- `results/open3d_fixed_vs_adaptive_alpha/aggregated_global.csv`
- `results/open3d_fixed_vs_adaptive_alpha/per_file_win_loss_summary.csv`

## Method

RANSAC-LP4 is a plane-detection method that reduces expensive full point-to-plane evaluations while keeping robustness to outliers.

Instead of sampling planes directly from three points, it first samples lines (two-point hypotheses), then constructs plane candidates from pairs of promising lines.

The method has three stages:

1. Candidate line generation:
   sample `n` lines, score each line by inlier count with threshold `t`, keep `N_l = floor(alpha * n)` best lines.
2. Candidate plane generation from line pairs:
   generate all unique pairs of retained lines, fit one plane per pair, rank by local fit quality (SSE), keep `N_pi = floor(beta * N_lambda)` where `N_lambda = N_l * (N_l - 1) / 2`.
3. Final plane evaluation:
   evaluate only retained candidate planes on the full point cloud and select the plane with highest inlier support.

Parameters:

- Shared with classical RANSAC: `n`, `t`
- Specific to RANSAC-LP4: `alpha`, `beta`

Adaptive line-selection option:

- Fixed mode (`line_selection_mode="fixed_alpha"`): keeps the top `alpha` fraction by line support.
- Adaptive mode (`line_selection_mode="adaptive_poisson_mixture"`): fits a 2-component Poisson mixture to line supports and keeps lines with posterior `P(good | support) >= tau`.
- `tau` controls strictness: higher `tau` keeps fewer lines.
- The effective retained fraction is `alpha_adapt = kept_lines / n`.
- Adaptive mode exposes `rho_hat = sqrt(w)`, where `w` is the estimated mixture weight of the “good-line” component.

Limitations:

- The adaptive selector assumes line supports are reasonably modeled by a 2-component Poisson mixture.
- If supports are nearly constant or EM is unstable, the implementation falls back to deterministic top-support retention with minimum-line safeguards.

Repository implementation notes:

- Planes from two lines are fit with a four-point least-squares (SVD) fit.
- Step-2 quality uses four-point SSE.
- Final scoring uses standard inlier counting against threshold `t`.

## Current Research Status

Completed:

- baseline LP4 implementation
- adaptive alpha line selection
- reproducible Open3D fixed-vs-adaptive canonical experiment

Interpretation from completed evidence:

- Adaptive alpha improves best-plane quality in most files.
- Adaptive alpha usually increases runtime, consistent with keeping more lines and therefore increasing line-pair combinations.

Current pairing focus:

- compatibility-filtered pairing with angle and support-centroid distance gates
- four-way Open3D ablation:
  - fixed alpha
  - adaptive alpha
  - fixed alpha + compatibility-filtered pairing
  - adaptive alpha + compatibility-filtered pairing
- completed angle-gate isolation ablation:
  - `adaptive_alpha`
  - `adaptive_alpha_cf_pairing_angle_only`
  - `adaptive_alpha_cf_pairing_angle_plus_distance` (`pairing_max_centroid_distance=3.0`)

Prepared artifacts:

- script: `experiments/run_open3d_pairing_ablation.py`
- script: `experiments/run_open3d_angle_only_ablation.py`
- script: `experiments/analyze_pairing_ablation_results.py`
- canonical snapshot folders:
  - `results/open3d_pairing_ablation_06/`
  - `results/open3d_pairing_ablation_pairing_max_centroid_20/`
  - `results/open3d_pairing_ablation_pairing_max_centroid_30/`
- method note: `docs/open3d_pairing_ablation.md`
- research report: `docs/research_report.md`

Next validation target:

- minimal S3DIS validation with:
  - `fixed_alpha`
  - `adaptive_alpha`
  - `adaptive_alpha_cf_pairing_angle_only`
- pilot-first execution on `Area_1`
- full-dataset execution only after pilot review

## CUDA backend

The project uses a native C++ CUDA shared library for CUDA execution. Numba is
not required.

Native backend coverage includes:

- single plane inlier counting
- batched plane inlier counting
- single line inlier counting
- plane inlier index collection

Runtime behavior:

- `ransaclpplus.ransaccuda` preserves the existing Python helper names while
  routing all CUDA work through `ransaclpplus.nativecuda`.
- `use_cuda=True` requires the native shared library. A clear runtime error is
  raised when it is unavailable.

### Building the native CUDA backend

Build from `ransaclpplus/native/cuda`:

```bash
cmake -S . -B build
cmake --build build -j
```

The Python loader checks:

- `RANSACLPPLUS_CUDA_LIB` (if set)
- `ransaclpplus/native/cuda/build/libransaclpplus_cuda.so` (Linux)
- `ransaclpplus/native/cuda/build/libransaclpplus_cuda.dylib` (macOS)
- `ransaclpplus/native/cuda/build/Release/ransaclpplus_cuda.dll` (Windows)

Quick verification:

```bash
python -c "from ransaclpplus.nativecuda import native_backend_available; print(native_backend_available())"
```

## CUDA benchmark

Benchmark native CUDA kernels and persistent-context cache behavior with:

```bash
python -m ransaclpplus.benchmark_cuda_backends --points 200000 --planes 64 --warmup 2 --repetitions 10
```

Optional JSON output:

```bash
python -m ransaclpplus.benchmark_cuda_backends --json-out benchmark_results.json
```

Historical migration measurements comparing native CUDA with the former Numba
backend are preserved in:

- `docs/reports/cuda_vs_numba_report.md`
- `docs/reports/cuda_numba_analysis.json`
- `docs/reports/cuda_numba_analysis_after_async_pinned.json` (latest optimized baseline)
- `docs/reports/cuda_numba_analysis_after_plane_cache.json` (latest plane-cache run)
- `docs/reports/cuda_optimization_comparison.md` (cross-version comparison)
- `docs/reports/optimization_backups/` (source snapshots by optimization stage)

Backup and artifact policy:

- Keep historical benchmark JSON files; append new versioned artifacts instead of overwriting old ones.
- Keep snapshot copies of changed optimization source files in `docs/reports/optimization_backups/` before major tuning changes.
- Prefer explicit version/date suffixes in artifact names for reproducible cross-version comparisons.

Historical optimized baseline summary (native over former Numba backend,
plane-cache run):

- Small (`4096` points, `12` planes): plane `15.42x`, line `14.83x`, batched `32.42x`
- Medium (`50000` points, `32` planes): plane `16.19x`, line `15.39x`, batched `7.29x`
- Large (`200000` points, `64` planes): plane `9.68x`, line `10.22x`, batched `2.27x`

If the package is installed with scripts, the equivalent command is:

```bash
ransaclpplus-benchmark-cuda --points 200000 --planes 64
```

## Campaigns

The repository now includes paper-oriented LP4 campaign families for Open3D and S3DIS.
The broader campaign roots remain reusable for reruns, and the compact minimum
paper-oriented subset has already been executed in versioned result roots.

Reusable campaign roots:

- `results/open3d_core_robustness/`
- `results/s3dis_core_robustness/`
- `results/open3d_adaptive_tau_sweep/`
- `results/s3dis_adaptive_tau_sweep/`
- `results/open3d_budget_sweep/`
- `results/s3dis_budget_sweep/`
- `results/s3dis_preprocessing_stability/`
- `results/open3d_vs_open3d_ransac/`
- `results/s3dis_vs_open3d_ransac/`
- `results/open3d_vs_open3d_ransac_lo/`
- `results/s3dis_vs_open3d_ransac_lo/`

Campaign entrypoints:

- `experiments/run_open3d_core_robustness.py`
- `experiments/run_s3dis_core_robustness.py`
- `experiments/run_open3d_adaptive_tau_sweep.py`
- `experiments/run_s3dis_adaptive_tau_sweep.py`
- `experiments/run_open3d_budget_sweep.py`
- `experiments/run_s3dis_budget_sweep.py`
- `experiments/run_s3dis_preprocessing_stability.py`
- `experiments/run_open3d_vs_open3d_ransac.py`
- `experiments/run_s3dis_vs_open3d_ransac.py`
- `experiments/run_open3d_vs_open3d_ransac_lo.py`
- `experiments/run_s3dis_vs_open3d_ransac_lo.py`

Unified post-analysis entrypoint:

- `experiments/lp4_campaign_analysis.py`

Primary metrics:

- Open3D: `best_plane_inliers` / `best_plane_inlier_ratio`
- S3DIS: raw-cloud final support via `best_plane_inliers_raw`, also mapped to top-level `best_plane_inliers`

External baseline policy:

- Plain external baseline: Open3D `segment_plane`
- LO-style external baseline: Open3D `segment_plane` plus local inlier refit rounds
- `open3d_segment_plane_lo` is a local-optimization proxy baseline, not a formal true LO-RANSAC implementation
- Hypothesis generation runs on the designated search-stage point set
- Final support is evaluated with the same final metric convention as the corresponding LP4 campaign
- For S3DIS, raw-cloud final support remains the primary reported metric
- Baseline parameters are written explicitly to `experiment_config.json` and `raw_runs.csv`

Manual run commands:

```bash
python experiments/run_open3d_core_robustness.py --output-dir results/open3d_core_robustness --num-runs 30 --n-line-iterations 250 --threshold 0.03 --beta 0.05 --fixed-alpha 0.2 --adaptive-tau 0.5 --pairing-min-angle-deg 10 --pairing-max-angle-deg 170 --use-cuda
python experiments/run_s3dis_core_robustness.py --output-dir results/s3dis_core_robustness --num-runs 30 --n-line-iterations 250 --threshold 0.03 --beta 0.05 --fixed-alpha 0.2 --adaptive-tau 0.5 --pairing-min-angle-deg 10 --pairing-max-angle-deg 170 --voxel-size 0.02 --search-subset-size 30000 --search-subset-seed 0 --use-cuda
python experiments/run_open3d_adaptive_tau_sweep.py --output-dir results/open3d_adaptive_tau_sweep --num-runs 10 --n-line-iterations 250 --threshold 0.03 --beta 0.05 --fixed-alpha 0.2 --pairing-min-angle-deg 10 --pairing-max-angle-deg 170 --use-cuda
python experiments/run_s3dis_adaptive_tau_sweep.py --output-dir results/s3dis_adaptive_tau_sweep --num-runs 10 --n-line-iterations 250 --threshold 0.03 --beta 0.05 --fixed-alpha 0.2 --pairing-min-angle-deg 10 --pairing-max-angle-deg 170 --voxel-size 0.02 --search-subset-size 30000 --search-subset-seed 0 --use-cuda
python experiments/run_open3d_budget_sweep.py --output-dir results/open3d_budget_sweep --num-runs 10 --threshold 0.03 --beta 0.05 --fixed-alpha 0.2 --adaptive-tau 0.5 --pairing-min-angle-deg 10 --pairing-max-angle-deg 170 --use-cuda
python experiments/run_s3dis_budget_sweep.py --output-dir results/s3dis_budget_sweep --num-runs 10 --threshold 0.03 --beta 0.05 --fixed-alpha 0.2 --adaptive-tau 0.5 --pairing-min-angle-deg 10 --pairing-max-angle-deg 170 --voxel-size 0.02 --search-subset-size 30000 --search-subset-seed 0 --use-cuda
python experiments/run_s3dis_preprocessing_stability.py --output-dir results/s3dis_preprocessing_stability --num-runs 10 --n-line-iterations 250 --threshold 0.03 --beta 0.05 --fixed-alpha 0.2 --adaptive-tau 0.5 --pairing-min-angle-deg 10 --pairing-max-angle-deg 170 --voxel-size 0.02 --search-subset-size 30000 --search-subset-seed 0 --use-cuda
```

External-baseline run commands:

```bash
python experiments/run_open3d_vs_open3d_ransac.py --output-dir results/open3d_vs_open3d_ransac --num-runs 10 --n-line-iterations 250 --threshold 0.03 --beta 0.05 --fixed-alpha 0.2 --adaptive-tau 0.5 --pairing-min-angle-deg 10 --pairing-max-angle-deg 170 --baseline-ransac-n 3 --baseline-lo-refit-max-rounds 2 --use-cuda
python experiments/run_s3dis_vs_open3d_ransac.py --output-dir results/s3dis_vs_open3d_ransac --num-runs 10 --n-line-iterations 250 --threshold 0.03 --beta 0.05 --fixed-alpha 0.2 --adaptive-tau 0.5 --pairing-min-angle-deg 10 --pairing-max-angle-deg 170 --voxel-size 0.02 --search-subset-size 30000 --search-subset-seed 0 --baseline-ransac-n 3 --baseline-lo-refit-max-rounds 2 --use-cuda
python experiments/run_open3d_vs_open3d_ransac_lo.py --output-dir results/open3d_vs_open3d_ransac_lo --num-runs 10 --n-line-iterations 250 --threshold 0.03 --beta 0.05 --fixed-alpha 0.2 --adaptive-tau 0.5 --pairing-min-angle-deg 10 --pairing-max-angle-deg 170 --baseline-ransac-n 3 --baseline-lo-refit-max-rounds 2 --use-cuda
python experiments/run_s3dis_vs_open3d_ransac_lo.py --output-dir results/s3dis_vs_open3d_ransac_lo --num-runs 10 --n-line-iterations 250 --threshold 0.03 --beta 0.05 --fixed-alpha 0.2 --adaptive-tau 0.5 --pairing-min-angle-deg 10 --pairing-max-angle-deg 170 --voxel-size 0.02 --search-subset-size 30000 --search-subset-seed 0 --baseline-ransac-n 3 --baseline-lo-refit-max-rounds 2 --use-cuda
```

Analysis commands:

```bash
python experiments/lp4_campaign_analysis.py --results-dir results/open3d_core_robustness --plot-format png
python experiments/lp4_campaign_analysis.py --results-dir results/s3dis_core_robustness --plot-format png
python experiments/lp4_campaign_analysis.py --results-dir results/open3d_adaptive_tau_sweep --plot-format png
python experiments/lp4_campaign_analysis.py --results-dir results/s3dis_adaptive_tau_sweep --plot-format png
python experiments/lp4_campaign_analysis.py --results-dir results/open3d_budget_sweep --plot-format png
python experiments/lp4_campaign_analysis.py --results-dir results/s3dis_budget_sweep --plot-format png
python experiments/lp4_campaign_analysis.py --results-dir results/s3dis_preprocessing_stability --plot-format png
python experiments/lp4_campaign_analysis.py --results-dir results/open3d_vs_open3d_ransac --plot-format png
python experiments/lp4_campaign_analysis.py --results-dir results/s3dis_vs_open3d_ransac --plot-format png
python experiments/lp4_campaign_analysis.py --results-dir results/open3d_vs_open3d_ransac_lo --plot-format png
python experiments/lp4_campaign_analysis.py --results-dir results/s3dis_vs_open3d_ransac_lo --plot-format png
```

Command-printer helpers:

- `scripts/print_open3d_core_commands.sh`
- `scripts/print_s3dis_core_commands.sh`
- `scripts/print_sweep_commands.sh`

Executed minimum paper-oriented result roots:

- `results/synthetic_lp4_benchmark_paper_minimum_20260313/`
- `results/open3d_budget_sweep_paper_minimum_20260313/`
- `results/open3d_lp4_proposal_lo_paper_minimum_20260313/`

## Documentation with Sphinx

This repository includes a Sphinx configuration under `docs/` for the package
reference and the main project guides.

Install documentation dependencies:

```bash
pip install -e ".[docs]"
```

Build HTML docs:

```bash
make -C docs html
```

The generated site will be available at `docs/_build/html/index.html`.
It includes:

- repository overview and navigation page
- API reference for the package modules
- method overview
- research report
- reproducibility guide
- benchmark and report pages under `docs/reports/`

The repository also includes Read the Docs deployment configuration. See the
[Read the Docs deployment guide](https://github.com/jmartinezot/lp4-plus/blob/main/docs/read_the_docs.md)
for the one-time project import and build settings.

Documentation expectations for ongoing development:

- new public functionality should be documented with Sphinx-compatible docstrings
- new user-visible workflows or features should be reflected in the relevant
  page under `docs/`

## Tests

The repository uses `pytest` for automated tests.

Run the full suite:

```bash
pytest -q
```

Run a focused test file:

```bash
pytest -q tests/test_ransaclpplus_api.py
```

Current baseline status in this environment:

- broad suite: `91 passed, 5 skipped`

Testing expectations for ongoing development:

- behavior changes should include updated or new automated tests
- new public additions and user-visible workflows should ship with both tests
  and Sphinx documentation in the same change

## Visual Step-by-Step Test (Open3D)

A manual visual test is available at `tests/test_ransaclpplus_visual_steps.py`.
It opens Open3D windows for:

- Step 1: sampled lines retained after line scoring
- Step 2: candidate planes generated from retained line pairs
- Step 3: final inliers of the best-scoring plane

Run it with:

```bash
RANSACLPPLUS_VISUAL_TEST=1 pytest -m visual -s
```

Or use the helper script:

```bash
./scripts/run_visual_test.sh
```

Close each Open3D window to continue to the next stage.

If you run inside the devcontainer, X11 forwarding must be enabled on your host.

## Run The Devcontainer From CLI (JSON-Equivalent)

The `.devcontainer/devcontainer.json` configuration can be reproduced from the command line with the following commands.

Exact `devcontainer.json` parity (including `features`) via Dev Container CLI:

```bash
devcontainer up --workspace-folder .
```

Plain Docker workflow approximating the build, run, and post-create settings:

Build the image from the same Dockerfile/context:

```bash
docker build -f .devcontainer/Dockerfile -t ransaclpplus-dev .
```

Run the container with the same GPU, env vars, X11, and workspace mount behavior:

```bash
REPOSITORY_ROOT="$(pwd)"
WORKSPACE_NAME="$(basename "${REPOSITORY_ROOT}")"
HOST_S3DIS_DATA_ROOT="${S3DIS_DATA_ROOT:-${HOME}/S3DIS/Stanford3dDataset_v1.2}"

docker run --rm -it \
  --gpus=all \
  --user vscode \
  --env DISPLAY="${DISPLAY}" \
  --env NVIDIA_VISIBLE_DEVICES=all \
  --env NVIDIA_DRIVER_CAPABILITIES=compute,utility \
  --env QT_X11_NO_MITSHM=1 \
  --env RANSACLPPLUS_CUDA_ARCH=61 \
  --env OPEN3D_DATA_ROOT=/home/vscode/open3d_data \
  --env S3DIS_DATA_ROOT=/datasets/S3DIS \
  --volume /tmp/.X11-unix:/tmp/.X11-unix:rw \
  --mount "type=bind,source=${REPOSITORY_ROOT},target=/workspaces/${WORKSPACE_NAME}" \
  --mount "type=bind,source=${HOST_S3DIS_DATA_ROOT},target=/datasets/S3DIS,readonly" \
  --workdir "/workspaces/${WORKSPACE_NAME}" \
  --name ransaclpplus-dev \
  ransaclpplus-dev \
  bash
```

Run the same post-create setup command defined in `devcontainer.json`:

```bash
docker exec -u vscode -it ransaclpplus-dev bash -lc \
  'pip install -e . && cmake -S ransaclpplus/native/cuda -B ransaclpplus/native/cuda/build -DCMAKE_CUDA_ARCHITECTURES=${RANSACLPPLUS_CUDA_ARCH} && cmake --build ransaclpplus/native/cuda/build -j'
```

Notes:

- Run the plain Docker commands from the repository root. Set
  `S3DIS_DATA_ROOT` on the host when the dataset is not stored at the default
  path shown above.
- The plain Docker workflow mirrors the core `runArgs`, `containerEnv`,
  `workspaceMount`, `workspaceFolder`, `remoteUser`, and `postCreateCommand`
  settings. Only the Dev Container CLI command applies `features` and provides
  exact `devcontainer.json` behavior.
- For Open3D windows from inside the container, host X11 access is required (for example, `xhost +local:docker` on Linux before running, and revert later with `xhost -local:docker`).
