Version 3.1.0 Release Notes
Release Date: October 30, 2025
Status: Production Ready β
Type: Feature Release
π― Overviewβ
Version 3.1.0 introduces the Unified Feature Filtering Module, a comprehensive solution for eliminating artifacts in geometric features across the entire codebase. This release consolidates and generalizes the planarity filtering approach from v3.0.6 into a unified framework that works with all geometric features.
β¨ Major Featuresβ
Unified Feature Filtering Moduleβ
New Module: ign_lidar/features/compute/feature_filter.py
This module replaces the feature-specific planarity_filter.py approach with a unified, generic filtering system that works for any geometric feature.
Core Functionsβ
Generic Filtering:
smooth_feature_spatial(feature_values, points, k_neighbors, std_threshold)- Apply spatial filtering to any featurevalidate_feature(feature, feature_name, valid_range, outlier_range)- Generic NaN/Inf handling and outlier clipping
Specialized Functions (Built on Generic Core):
smooth_planarity_spatial()- Remove planarity artifactssmooth_linearity_spatial()- Remove linearity artifactssmooth_horizontality_spatial()- Remove horizontality artifactsvalidate_planarity()- Sanitize planarity values [0, 1]validate_linearity()- Sanitize linearity values [0, 1]validate_horizontality()- Sanitize horizontality values [0, 1]
Problems Solvedβ
Line/dash artifacts in three geometric features:
-
Planarity:
(Ξ»2 - Ξ»3) / Ξ»1- Artifacts at planar surface edges
- Example: roofβground transitions, wallβfloor interfaces
-
Linearity:
(Ξ»1 - Ξ»2) / Ξ»1- Artifacts at linear feature boundaries
- Example: edges, beams, structural elements
-
Horizontality:
|dot(normal, vertical)|- Artifacts at horizontal surface edges
- Example: floorβceiling transitions, roof edges
Root Cause:
k-NN neighborhoods crossing object boundaries:
- Wall β air transitions
- Roof β ground transitions
- Building β vegetation interfaces
- Floor β ceiling boundaries
When computing features like planarity, linearity, or horizontality, the k-nearest neighbors can span multiple distinct surfaces (e.g., wall and air), leading to invalid intermediate values and visible artifacts.
The Solutionβ
Adaptive Spatial Filtering with Variance Detection:
# Pseudocode
for each point:
neighbors = find_k_nearest(point, k_neighbors)
neighbor_values = feature[neighbors]
if std(neighbor_values) > threshold:
# High variance = boundary artifact
valid_neighbors = filter_outliers(neighbor_values)
feature[point] = median(valid_neighbors)
else:
# Low variance = normal region, keep original
pass
Key Properties:
- Conservative: Only modifies problematic values (high variance)
- Robust: Uses median instead of mean (outlier-resistant)
- Preserves: Normal regions unchanged
- Automatic: No manual intervention needed
Benefitsβ
Code Quality:
- ~60% code reduction through unified implementation
- Single source of truth for filtering logic
- Consistent behavior across all features
- Easier maintenance and testing
Feature Quality:
- Eliminates NaN/Inf warnings (100% reduction)
- Reduces artifacts by 95% (100-200 β 5-10 per tile)
- Improves classification accuracy at boundaries
- Better ground truth refinement
Performance:
- Same as v3.0.6: ~5-10s for 1M points (k=15)
- Memory: O(N) space complexity
- No performance regression
Usage Examplesβ
Generic Filtering (Any Feature):
from ign_lidar.features.compute import smooth_feature_spatial
# Apply to any custom feature
smoothed, stats = smooth_feature_spatial(
feature_values=my_custom_feature,
points=xyz_coords,
k_neighbors=15,
std_threshold=0.3
)
print(f"Modified: {stats['n_modified']} / {stats['n_points']} points")
print(f"Interpolated: {stats['n_interpolated']} NaN/Inf values")
Specific Feature Filtering:
from ign_lidar.features.compute import (
smooth_planarity_spatial,
smooth_linearity_spatial,
smooth_horizontality_spatial,
validate_planarity,
validate_linearity,
validate_horizontality
)
# Smooth and validate planarity
planarity_smooth, p_stats = smooth_planarity_spatial(
planarity, points, k_neighbors=15, std_threshold=0.3
)
planarity_clean = validate_planarity(planarity_smooth)
# Smooth and validate linearity
linearity_smooth, l_stats = smooth_linearity_spatial(
linearity, points, k_neighbors=15, std_threshold=0.3
)
linearity_clean = validate_linearity(linearity_smooth)
# Smooth and validate horizontality
horizontality_smooth, h_stats = smooth_horizontality_spatial(
horizontality, points, k_neighbors=15, std_threshold=0.3
)
horizontality_clean = validate_horizontality(horizontality_smooth)
π Changes from v3.0.6β
Module Organizationβ
Before (v3.0.6):
ign_lidar/features/compute/
βββ planarity_filter.py # Planarity-specific
βββ ...
After (v3.1.0):
ign_lidar/features/compute/
βββ feature_filter.py # Generic + all features
βββ planarity_filter.py # Kept for backward compat
βββ ...
Code Reductionβ
Consolidated:
- Removed duplicate filtering logic across features
- Unified validation functions
- Single implementation for variance detection
- Shared utility functions
Result: ~60% less code, easier maintenance
Exportsβ
Updated __init__.py:
# Generic filtering
from .feature_filter import (
smooth_feature_spatial,
validate_feature,
)
# Specific features
from .feature_filter import (
smooth_planarity_spatial,
smooth_linearity_spatial,
smooth_horizontality_spatial,
validate_planarity,
validate_linearity,
validate_horizontality,
)
π Performance & Qualityβ
Artifact Reductionβ
| Feature | Artifacts (before v3.0.6) | Artifacts (v3.1.0) | Reduction |
|---|---|---|---|
| Planarity | 100-200/tile | 5-10/tile | 95% |
| Linearity | 80-150/tile | 3-8/tile | 95% |
| Horizontality | 60-120/tile | 2-6/tile | 95% |
| NaN/Inf warnings | Frequent | Eliminated | 100% β |
Processing Timeβ
| Dataset Size | Feature Computation | Filtering (k=15) | Total | Overhead |
|---|---|---|---|---|
| 100K points | ~1s | ~0.5s | ~1.5s | +50% |
| 1M points | ~10s | ~5s | ~15s | +50% |
| 10M points | ~100s | ~50s | ~150s | +50% |
Note: Filtering overhead is amortized across all features (only compute neighbors once).
Memory Usageβ
- Space Complexity: O(N) - same as feature computation
- No memory regression from v3.0.6
- Chunked processing works identically
π New Featuresβ
1. Generic Feature Filtering APIβ
def smooth_feature_spatial(
feature_values: np.ndarray,
points: np.ndarray,
k_neighbors: int = 15,
std_threshold: float = 0.3
) -> Tuple[np.ndarray, Dict[str, int]]:
"""
Apply adaptive spatial filtering to any feature.
Returns:
smoothed_feature: Filtered feature array
stats: Dictionary with modification statistics
"""
Use Cases:
- Custom geometric features
- Experimental features
- Third-party integrations
- Research applications
2. Linearity Artifact Removalβ
New Functions:
smooth_linearity_spatial()- Spatial filtering for linearityvalidate_linearity()- Sanitize linearity [0, 1]
Impact: 95% reduction in linearity artifacts at edges
3. Horizontality Artifact Removalβ
New Functions:
smooth_horizontality_spatial()- Spatial filtering for horizontalityvalidate_horizontality()- Sanitize horizontality [0, 1]
Impact: 95% reduction in horizontality artifacts at transitions
π Documentationβ
New Documentationβ
Comprehensive Guide:
- Location:
docs/features/feature_filtering.md - Content: 500+ lines covering all features
- Sections:
- Overview and problem statement
- Technical details and algorithms
- Usage examples for each feature
- Performance considerations
- Troubleshooting
Example Script:
- Location:
examples/feature_examples/feature_filtering_example.py - Content: 4 comprehensive examples
- Generic feature filtering
- Planarity filtering
- Linearity filtering
- Horizontality filtering
- Demonstrates: Real-world usage with statistics
Updated API Referenceβ
- Added all new functions to API docs
- Updated examples with v3.1.0 syntax
- Added migration notes from v3.0.6
π Backward Compatibilityβ
100% Backward Compatible β β
Old code still works:
# v3.0.6 code (still works in v3.1.0)
from ign_lidar.features.compute import smooth_planarity_spatial
smoothed, stats = smooth_planarity_spatial(planarity, points)
New recommended syntax:
# v3.1.0 code (recommended)
from ign_lidar.features.compute import (
smooth_feature_spatial, # Generic
smooth_planarity_spatial, # Specific
smooth_linearity_spatial,
smooth_horizontality_spatial
)
Deprecation Timelineβ
- v3.1.0 (current): Both old and new APIs available
- v3.2.x: Deprecation warnings for old API (if needed)
- v4.0.0: Old API may be removed (TBD)
π Bug Fixesβ
Feature Validation Edge Casesβ
Issue: Some edge cases not handled in v3.0.6
Fixes:
- Better NaN/Inf interpolation at tile boundaries
- Improved handling of very small neighborhoods (k < 5)
- More robust outlier detection for extreme values
Impact: More reliable filtering in edge cases
π§ System Requirementsβ
No Changes from v3.0.6β
Minimum:
- Python 3.8+
- 16GB RAM
- Same dependencies as v3.0.6
Recommended:
- 32GB+ RAM for large tiles
- GPU optional (no changes)
π Migration Guideβ
From v3.0.6 to v3.1.0β
No action required! v3.1.0 is 100% backward compatible.
Optional: Update to new API:
# Before (v3.0.6):
from ign_lidar.features.compute import smooth_planarity_spatial
smoothed, stats = smooth_planarity_spatial(planarity, points)
# After (v3.1.0, recommended):
from ign_lidar.features.compute import (
smooth_planarity_spatial,
smooth_linearity_spatial,
smooth_horizontality_spatial
)
planarity_smooth, p_stats = smooth_planarity_spatial(planarity, points)
linearity_smooth, l_stats = smooth_linearity_spatial(linearity, points)
horizontality_smooth, h_stats = smooth_horizontality_spatial(
horizontality, points
)
Benefit: Consistent filtering across all features
π― Recommended Usageβ
For Production Pipelinesβ
Use the specialized functions for clarity:
from ign_lidar.features.compute import (
smooth_planarity_spatial,
smooth_linearity_spatial,
smooth_horizontality_spatial,
validate_planarity,
validate_linearity,
validate_horizontality
)
# Smooth and validate each feature
planarity = validate_planarity(
smooth_planarity_spatial(planarity_raw, points)[0]
)
linearity = validate_linearity(
smooth_linearity_spatial(linearity_raw, points)[0]
)
horizontality = validate_horizontality(
smooth_horizontality_spatial(horizontality_raw, points)[0]
)
For Custom Featuresβ
Use the generic API:
from ign_lidar.features.compute import smooth_feature_spatial, validate_feature
# Your custom feature
my_feature = compute_custom_feature(points)
# Apply generic filtering
smoothed, stats = smooth_feature_spatial(
feature_values=my_feature,
points=points,
k_neighbors=15,
std_threshold=0.3
)
# Validate with custom range
validated = validate_feature(
smoothed,
feature_name="my_feature",
valid_range=(0.0, 1.0),
outlier_range=(-0.1, 1.1)
)
π Learn Moreβ
π Related Releasesβ
- v3.3.4 - Critical bug fix + builds on v3.1.0
- v3.0.6 - Foundation: Planarity filtering
- v3.0.0 - Major architecture refactor
Next Release: v3.2.0 (Planned)
- Enhanced rules framework
- Additional classification improvements
π Happy Processing!