sdf-sampler 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sdf_sampler-0.1.0/.github_token.env +1 -0
- sdf_sampler-0.1.0/.gitignore +81 -0
- sdf_sampler-0.1.0/CHANGELOG.md +44 -0
- sdf_sampler-0.1.0/LICENSE +21 -0
- sdf_sampler-0.1.0/PKG-INFO +226 -0
- sdf_sampler-0.1.0/README.md +182 -0
- sdf_sampler-0.1.0/pyproject.toml +75 -0
- sdf_sampler-0.1.0/src/sdf_sampler/__init__.py +78 -0
- sdf_sampler-0.1.0/src/sdf_sampler/algorithms/__init__.py +19 -0
- sdf_sampler-0.1.0/src/sdf_sampler/algorithms/flood_fill.py +233 -0
- sdf_sampler-0.1.0/src/sdf_sampler/algorithms/normal_idw.py +99 -0
- sdf_sampler-0.1.0/src/sdf_sampler/algorithms/normal_offset.py +111 -0
- sdf_sampler-0.1.0/src/sdf_sampler/algorithms/pocket.py +146 -0
- sdf_sampler-0.1.0/src/sdf_sampler/algorithms/voxel_grid.py +339 -0
- sdf_sampler-0.1.0/src/sdf_sampler/algorithms/voxel_regions.py +80 -0
- sdf_sampler-0.1.0/src/sdf_sampler/analyzer.py +299 -0
- sdf_sampler-0.1.0/src/sdf_sampler/config.py +171 -0
- sdf_sampler-0.1.0/src/sdf_sampler/io.py +178 -0
- sdf_sampler-0.1.0/src/sdf_sampler/models/__init__.py +49 -0
- sdf_sampler-0.1.0/src/sdf_sampler/models/analysis.py +85 -0
- sdf_sampler-0.1.0/src/sdf_sampler/models/constraints.py +192 -0
- sdf_sampler-0.1.0/src/sdf_sampler/models/samples.py +49 -0
- sdf_sampler-0.1.0/src/sdf_sampler/sampler.py +439 -0
- sdf_sampler-0.1.0/src/sdf_sampler/sampling/__init__.py +15 -0
- sdf_sampler-0.1.0/src/sdf_sampler/sampling/box.py +131 -0
- sdf_sampler-0.1.0/src/sdf_sampler/sampling/brush.py +63 -0
- sdf_sampler-0.1.0/src/sdf_sampler/sampling/ray_carve.py +134 -0
- sdf_sampler-0.1.0/src/sdf_sampler/sampling/sphere.py +57 -0
- sdf_sampler-0.1.0/tests/__init__.py +2 -0
- sdf_sampler-0.1.0/tests/test_analyzer.py +246 -0
- sdf_sampler-0.1.0/tests/test_equivalence.py +761 -0
- sdf_sampler-0.1.0/tests/test_integration.py +265 -0
- sdf_sampler-0.1.0/tests/test_models.py +189 -0
- sdf_sampler-0.1.0/tests/test_sampler.py +231 -0
- sdf_sampler-0.1.0/uv.lock +1071 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
GITHUB_TOKEN=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
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
lib/
|
|
18
|
+
lib64/
|
|
19
|
+
parts/
|
|
20
|
+
sdist/
|
|
21
|
+
var/
|
|
22
|
+
wheels/
|
|
23
|
+
*.egg-info/
|
|
24
|
+
.installed.cfg
|
|
25
|
+
*.egg
|
|
26
|
+
|
|
27
|
+
# PyInstaller
|
|
28
|
+
*.manifest
|
|
29
|
+
*.spec
|
|
30
|
+
|
|
31
|
+
# Installer logs
|
|
32
|
+
pip-log.txt
|
|
33
|
+
pip-delete-this-directory.txt
|
|
34
|
+
|
|
35
|
+
# Unit test / coverage reports
|
|
36
|
+
htmlcov/
|
|
37
|
+
.tox/
|
|
38
|
+
.nox/
|
|
39
|
+
.coverage
|
|
40
|
+
.coverage.*
|
|
41
|
+
.cache
|
|
42
|
+
nosetests.xml
|
|
43
|
+
coverage.xml
|
|
44
|
+
*.cover
|
|
45
|
+
*.py,cover
|
|
46
|
+
.hypothesis/
|
|
47
|
+
.pytest_cache/
|
|
48
|
+
|
|
49
|
+
# Translations
|
|
50
|
+
*.mo
|
|
51
|
+
*.pot
|
|
52
|
+
|
|
53
|
+
# Environments
|
|
54
|
+
.env
|
|
55
|
+
.venv
|
|
56
|
+
env/
|
|
57
|
+
venv/
|
|
58
|
+
ENV/
|
|
59
|
+
env.bak/
|
|
60
|
+
venv.bak/
|
|
61
|
+
|
|
62
|
+
# IDEs
|
|
63
|
+
.idea/
|
|
64
|
+
.vscode/
|
|
65
|
+
*.swp
|
|
66
|
+
*.swo
|
|
67
|
+
|
|
68
|
+
# mypy
|
|
69
|
+
.mypy_cache/
|
|
70
|
+
.dmypy.json
|
|
71
|
+
dmypy.json
|
|
72
|
+
|
|
73
|
+
# Ruff
|
|
74
|
+
.ruff_cache/
|
|
75
|
+
|
|
76
|
+
# Local development
|
|
77
|
+
*.parquet
|
|
78
|
+
*.npz
|
|
79
|
+
|
|
80
|
+
# Secrets
|
|
81
|
+
.pypi_token.env
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to sdf-sampler will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [0.1.0] - 2025-01-29
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Initial release extracted from sdf-labeler backend
|
|
13
|
+
- **SDFAnalyzer** class for auto-analysis of point clouds
|
|
14
|
+
- `flood_fill` algorithm: EMPTY region detection via ray propagation from sky
|
|
15
|
+
- `voxel_regions` algorithm: SOLID region detection from underground
|
|
16
|
+
- `normal_offset` algorithm: Paired SOLID/EMPTY boxes along surface normals
|
|
17
|
+
- `normal_idw` algorithm: Inverse distance weighted sampling along normals
|
|
18
|
+
- `pocket` algorithm: Interior cavity detection via voxel flood fill
|
|
19
|
+
- Hull filtering to remove constraints outside X-Y alpha shape
|
|
20
|
+
- Configurable via `AnalyzerConfig` dataclass
|
|
21
|
+
- **SDFSampler** class for training sample generation
|
|
22
|
+
- Supports multiple constraint types: box, sphere, halfspace, brush_stroke, etc.
|
|
23
|
+
- Three sampling strategies: CONSTANT, DENSITY, INVERSE_SQUARE
|
|
24
|
+
- Export to Parquet and DataFrame
|
|
25
|
+
- Configurable via `SamplerConfig` dataclass
|
|
26
|
+
- **I/O helpers**
|
|
27
|
+
- `load_point_cloud()` for PLY, LAS/LAZ, CSV, NPZ, Parquet formats
|
|
28
|
+
- `export_parquet()` for survi-compatible training data export
|
|
29
|
+
- Pydantic models for all constraint types and training samples
|
|
30
|
+
- Comprehensive test suite (56 tests including equivalence tests vs sdf-labeler backend)
|
|
31
|
+
|
|
32
|
+
### Dependencies
|
|
33
|
+
|
|
34
|
+
- pydantic>=2.5.0 (data validation)
|
|
35
|
+
- numpy>=1.26.0 (core arrays)
|
|
36
|
+
- pandas>=2.1.0 (Parquet I/O)
|
|
37
|
+
- scipy>=1.11.0 (KDTree, spatial, ndimage)
|
|
38
|
+
- alphashape>=1.3.1 (concave hull filtering)
|
|
39
|
+
- pyarrow>=14.0.0 (Parquet export)
|
|
40
|
+
|
|
41
|
+
### Optional Dependencies
|
|
42
|
+
|
|
43
|
+
- trimesh>=4.0.0 (PLY loading, `[io]` extra)
|
|
44
|
+
- laspy[laszip]>=2.5.0 (LAS/LAZ loading, `[io]` extra)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Liam
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sdf-sampler
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Auto-analysis and sampling of point clouds for SDF (Signed Distance Field) training data generation
|
|
5
|
+
Project-URL: Repository, https://github.com/chiark/sdf-sampler
|
|
6
|
+
Author-email: Liam <liam@example.com>
|
|
7
|
+
License: MIT
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Keywords: machine-learning,point-cloud,sampling,sdf,signed-distance-field
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Topic :: Scientific/Engineering
|
|
18
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
19
|
+
Requires-Python: >=3.11
|
|
20
|
+
Requires-Dist: alphashape>=1.3.1
|
|
21
|
+
Requires-Dist: numpy>=1.26.0
|
|
22
|
+
Requires-Dist: pandas>=2.1.0
|
|
23
|
+
Requires-Dist: pyarrow>=14.0.0
|
|
24
|
+
Requires-Dist: pydantic>=2.5.0
|
|
25
|
+
Requires-Dist: scipy>=1.11.0
|
|
26
|
+
Provides-Extra: all
|
|
27
|
+
Requires-Dist: laspy[laszip]>=2.5.0; extra == 'all'
|
|
28
|
+
Requires-Dist: mypy>=1.8.0; extra == 'all'
|
|
29
|
+
Requires-Dist: pytest-asyncio>=0.23.0; extra == 'all'
|
|
30
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == 'all'
|
|
31
|
+
Requires-Dist: pytest>=8.0.0; extra == 'all'
|
|
32
|
+
Requires-Dist: ruff>=0.5.0; extra == 'all'
|
|
33
|
+
Requires-Dist: trimesh>=4.0.0; extra == 'all'
|
|
34
|
+
Provides-Extra: dev
|
|
35
|
+
Requires-Dist: mypy>=1.8.0; extra == 'dev'
|
|
36
|
+
Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
|
|
37
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == 'dev'
|
|
38
|
+
Requires-Dist: pytest>=8.0.0; extra == 'dev'
|
|
39
|
+
Requires-Dist: ruff>=0.5.0; extra == 'dev'
|
|
40
|
+
Provides-Extra: io
|
|
41
|
+
Requires-Dist: laspy[laszip]>=2.5.0; extra == 'io'
|
|
42
|
+
Requires-Dist: trimesh>=4.0.0; extra == 'io'
|
|
43
|
+
Description-Content-Type: text/markdown
|
|
44
|
+
|
|
45
|
+
# sdf-sampler
|
|
46
|
+
|
|
47
|
+
Auto-analysis and sampling of point clouds for SDF (Signed Distance Field) training data generation.
|
|
48
|
+
|
|
49
|
+
A lightweight, standalone Python package for generating SDF training hints from point clouds. Automatically detects SOLID (inside) and EMPTY (outside) regions and generates training samples suitable for SDF regression models.
|
|
50
|
+
|
|
51
|
+
## Installation
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
pip install sdf-sampler
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
For additional I/O format support (PLY, LAS/LAZ):
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
pip install sdf-sampler[io]
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Quick Start
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
from sdf_sampler import SDFAnalyzer, SDFSampler, load_point_cloud
|
|
67
|
+
|
|
68
|
+
# 1. Load point cloud (supports PLY, LAS, CSV, NPZ, Parquet)
|
|
69
|
+
xyz, normals = load_point_cloud("scan.ply")
|
|
70
|
+
|
|
71
|
+
# 2. Auto-analyze to detect EMPTY/SOLID regions
|
|
72
|
+
analyzer = SDFAnalyzer()
|
|
73
|
+
result = analyzer.analyze(xyz=xyz, normals=normals)
|
|
74
|
+
print(f"Generated {len(result.constraints)} constraints")
|
|
75
|
+
|
|
76
|
+
# 3. Generate training samples
|
|
77
|
+
sampler = SDFSampler()
|
|
78
|
+
samples = sampler.generate(
|
|
79
|
+
xyz=xyz,
|
|
80
|
+
constraints=result.constraints,
|
|
81
|
+
strategy="inverse_square",
|
|
82
|
+
total_samples=50000,
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
# 4. Export to parquet
|
|
86
|
+
sampler.export_parquet(samples, "training_data.parquet")
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Features
|
|
90
|
+
|
|
91
|
+
### Auto-Analysis Algorithms
|
|
92
|
+
|
|
93
|
+
- **flood_fill**: Detects EMPTY (outside) regions by ray propagation from sky
|
|
94
|
+
- **voxel_regions**: Detects SOLID (underground) regions
|
|
95
|
+
- **normal_offset**: Generates paired SOLID/EMPTY boxes along surface normals
|
|
96
|
+
- **normal_idw**: Inverse distance weighted sampling along normals
|
|
97
|
+
- **pocket**: Detects interior cavities
|
|
98
|
+
|
|
99
|
+
### Sampling Strategies
|
|
100
|
+
|
|
101
|
+
- **CONSTANT**: Fixed number of samples per constraint
|
|
102
|
+
- **DENSITY**: Samples proportional to constraint volume
|
|
103
|
+
- **INVERSE_SQUARE**: More samples near surface, fewer far away (recommended)
|
|
104
|
+
|
|
105
|
+
## API Reference
|
|
106
|
+
|
|
107
|
+
### SDFAnalyzer
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
from sdf_sampler import SDFAnalyzer, AnalyzerConfig
|
|
111
|
+
|
|
112
|
+
# With default config
|
|
113
|
+
analyzer = SDFAnalyzer()
|
|
114
|
+
|
|
115
|
+
# With custom config
|
|
116
|
+
analyzer = SDFAnalyzer(config=AnalyzerConfig(
|
|
117
|
+
min_gap_size=0.10, # Minimum gap for flood fill
|
|
118
|
+
max_grid_dim=200, # Maximum voxel grid dimension
|
|
119
|
+
cone_angle=15.0, # Ray propagation cone angle
|
|
120
|
+
hull_filter_enabled=True, # Filter outside X-Y hull
|
|
121
|
+
))
|
|
122
|
+
|
|
123
|
+
# Run analysis
|
|
124
|
+
result = analyzer.analyze(
|
|
125
|
+
xyz=xyz, # (N, 3) point positions
|
|
126
|
+
normals=normals, # (N, 3) point normals (optional)
|
|
127
|
+
algorithms=["flood_fill", "voxel_regions"], # Which algorithms to run
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
# Access results
|
|
131
|
+
print(f"Total constraints: {result.summary.total_constraints}")
|
|
132
|
+
print(f"SOLID: {result.summary.solid_constraints}")
|
|
133
|
+
print(f"EMPTY: {result.summary.empty_constraints}")
|
|
134
|
+
|
|
135
|
+
# Get constraint dicts for sampling
|
|
136
|
+
constraints = result.constraints
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### SDFSampler
|
|
140
|
+
|
|
141
|
+
```python
|
|
142
|
+
from sdf_sampler import SDFSampler, SamplerConfig
|
|
143
|
+
|
|
144
|
+
# With default config
|
|
145
|
+
sampler = SDFSampler()
|
|
146
|
+
|
|
147
|
+
# With custom config
|
|
148
|
+
sampler = SDFSampler(config=SamplerConfig(
|
|
149
|
+
total_samples=10000,
|
|
150
|
+
inverse_square_base_samples=100,
|
|
151
|
+
inverse_square_falloff=2.0,
|
|
152
|
+
near_band=0.02,
|
|
153
|
+
))
|
|
154
|
+
|
|
155
|
+
# Generate samples
|
|
156
|
+
samples = sampler.generate(
|
|
157
|
+
xyz=xyz, # Point cloud for distance computation
|
|
158
|
+
constraints=constraints, # From analyzer.analyze().constraints
|
|
159
|
+
strategy="inverse_square", # Sampling strategy
|
|
160
|
+
seed=42, # For reproducibility
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
# Export
|
|
164
|
+
sampler.export_parquet(samples, "output.parquet")
|
|
165
|
+
|
|
166
|
+
# Or get DataFrame
|
|
167
|
+
df = sampler.to_dataframe(samples)
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### Constraint Types
|
|
171
|
+
|
|
172
|
+
The analyzer generates various constraint types:
|
|
173
|
+
|
|
174
|
+
- **BoxConstraint**: Axis-aligned bounding box
|
|
175
|
+
- **SphereConstraint**: Spherical region
|
|
176
|
+
- **SamplePointConstraint**: Direct point with signed distance
|
|
177
|
+
- **PocketConstraint**: Detected cavity region
|
|
178
|
+
|
|
179
|
+
Each constraint has:
|
|
180
|
+
- `sign`: "solid" (negative SDF) or "empty" (positive SDF)
|
|
181
|
+
- `weight`: Sample weight (default 1.0)
|
|
182
|
+
|
|
183
|
+
## Output Format
|
|
184
|
+
|
|
185
|
+
The exported parquet file contains columns:
|
|
186
|
+
|
|
187
|
+
| Column | Type | Description |
|
|
188
|
+
|--------|------|-------------|
|
|
189
|
+
| x, y, z | float | 3D position |
|
|
190
|
+
| phi | float | Signed distance (negative=solid, positive=empty) |
|
|
191
|
+
| nx, ny, nz | float | Normal vector (if available) |
|
|
192
|
+
| weight | float | Sample weight |
|
|
193
|
+
| source | string | Sample origin (e.g., "box_solid", "flood_fill_empty") |
|
|
194
|
+
| is_surface | bool | Whether sample is on surface |
|
|
195
|
+
| is_free | bool | Whether sample is in free space (EMPTY) |
|
|
196
|
+
|
|
197
|
+
## Configuration Options
|
|
198
|
+
|
|
199
|
+
### AnalyzerConfig
|
|
200
|
+
|
|
201
|
+
| Option | Default | Description |
|
|
202
|
+
|--------|---------|-------------|
|
|
203
|
+
| min_gap_size | 0.10 | Minimum gap size for flood fill (meters) |
|
|
204
|
+
| max_grid_dim | 200 | Maximum voxel grid dimension |
|
|
205
|
+
| cone_angle | 15.0 | Ray propagation cone half-angle (degrees) |
|
|
206
|
+
| normal_offset_pairs | 40 | Number of box pairs for normal_offset |
|
|
207
|
+
| idw_sample_count | 1000 | Total IDW samples |
|
|
208
|
+
| idw_max_distance | 0.5 | Maximum IDW distance (meters) |
|
|
209
|
+
| hull_filter_enabled | True | Filter outside X-Y alpha shape |
|
|
210
|
+
| hull_alpha | 1.0 | Alpha shape parameter |
|
|
211
|
+
|
|
212
|
+
### SamplerConfig
|
|
213
|
+
|
|
214
|
+
| Option | Default | Description |
|
|
215
|
+
|--------|---------|-------------|
|
|
216
|
+
| total_samples | 10000 | Default total samples |
|
|
217
|
+
| samples_per_primitive | 100 | Samples per constraint (CONSTANT) |
|
|
218
|
+
| samples_per_cubic_meter | 10000 | Sample density (DENSITY) |
|
|
219
|
+
| inverse_square_base_samples | 100 | Base samples (INVERSE_SQUARE) |
|
|
220
|
+
| inverse_square_falloff | 2.0 | Falloff exponent |
|
|
221
|
+
| near_band | 0.02 | Near-band width |
|
|
222
|
+
| seed | 0 | Random seed |
|
|
223
|
+
|
|
224
|
+
## License
|
|
225
|
+
|
|
226
|
+
MIT
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# sdf-sampler
|
|
2
|
+
|
|
3
|
+
Auto-analysis and sampling of point clouds for SDF (Signed Distance Field) training data generation.
|
|
4
|
+
|
|
5
|
+
A lightweight, standalone Python package for generating SDF training hints from point clouds. Automatically detects SOLID (inside) and EMPTY (outside) regions and generates training samples suitable for SDF regression models.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install sdf-sampler
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
For additional I/O format support (PLY, LAS/LAZ):
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pip install sdf-sampler[io]
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Quick Start
|
|
20
|
+
|
|
21
|
+
```python
|
|
22
|
+
from sdf_sampler import SDFAnalyzer, SDFSampler, load_point_cloud
|
|
23
|
+
|
|
24
|
+
# 1. Load point cloud (supports PLY, LAS, CSV, NPZ, Parquet)
|
|
25
|
+
xyz, normals = load_point_cloud("scan.ply")
|
|
26
|
+
|
|
27
|
+
# 2. Auto-analyze to detect EMPTY/SOLID regions
|
|
28
|
+
analyzer = SDFAnalyzer()
|
|
29
|
+
result = analyzer.analyze(xyz=xyz, normals=normals)
|
|
30
|
+
print(f"Generated {len(result.constraints)} constraints")
|
|
31
|
+
|
|
32
|
+
# 3. Generate training samples
|
|
33
|
+
sampler = SDFSampler()
|
|
34
|
+
samples = sampler.generate(
|
|
35
|
+
xyz=xyz,
|
|
36
|
+
constraints=result.constraints,
|
|
37
|
+
strategy="inverse_square",
|
|
38
|
+
total_samples=50000,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
# 4. Export to parquet
|
|
42
|
+
sampler.export_parquet(samples, "training_data.parquet")
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Features
|
|
46
|
+
|
|
47
|
+
### Auto-Analysis Algorithms
|
|
48
|
+
|
|
49
|
+
- **flood_fill**: Detects EMPTY (outside) regions by ray propagation from sky
|
|
50
|
+
- **voxel_regions**: Detects SOLID (underground) regions
|
|
51
|
+
- **normal_offset**: Generates paired SOLID/EMPTY boxes along surface normals
|
|
52
|
+
- **normal_idw**: Inverse distance weighted sampling along normals
|
|
53
|
+
- **pocket**: Detects interior cavities
|
|
54
|
+
|
|
55
|
+
### Sampling Strategies
|
|
56
|
+
|
|
57
|
+
- **CONSTANT**: Fixed number of samples per constraint
|
|
58
|
+
- **DENSITY**: Samples proportional to constraint volume
|
|
59
|
+
- **INVERSE_SQUARE**: More samples near surface, fewer far away (recommended)
|
|
60
|
+
|
|
61
|
+
## API Reference
|
|
62
|
+
|
|
63
|
+
### SDFAnalyzer
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
from sdf_sampler import SDFAnalyzer, AnalyzerConfig
|
|
67
|
+
|
|
68
|
+
# With default config
|
|
69
|
+
analyzer = SDFAnalyzer()
|
|
70
|
+
|
|
71
|
+
# With custom config
|
|
72
|
+
analyzer = SDFAnalyzer(config=AnalyzerConfig(
|
|
73
|
+
min_gap_size=0.10, # Minimum gap for flood fill
|
|
74
|
+
max_grid_dim=200, # Maximum voxel grid dimension
|
|
75
|
+
cone_angle=15.0, # Ray propagation cone angle
|
|
76
|
+
hull_filter_enabled=True, # Filter outside X-Y hull
|
|
77
|
+
))
|
|
78
|
+
|
|
79
|
+
# Run analysis
|
|
80
|
+
result = analyzer.analyze(
|
|
81
|
+
xyz=xyz, # (N, 3) point positions
|
|
82
|
+
normals=normals, # (N, 3) point normals (optional)
|
|
83
|
+
algorithms=["flood_fill", "voxel_regions"], # Which algorithms to run
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
# Access results
|
|
87
|
+
print(f"Total constraints: {result.summary.total_constraints}")
|
|
88
|
+
print(f"SOLID: {result.summary.solid_constraints}")
|
|
89
|
+
print(f"EMPTY: {result.summary.empty_constraints}")
|
|
90
|
+
|
|
91
|
+
# Get constraint dicts for sampling
|
|
92
|
+
constraints = result.constraints
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### SDFSampler
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
from sdf_sampler import SDFSampler, SamplerConfig
|
|
99
|
+
|
|
100
|
+
# With default config
|
|
101
|
+
sampler = SDFSampler()
|
|
102
|
+
|
|
103
|
+
# With custom config
|
|
104
|
+
sampler = SDFSampler(config=SamplerConfig(
|
|
105
|
+
total_samples=10000,
|
|
106
|
+
inverse_square_base_samples=100,
|
|
107
|
+
inverse_square_falloff=2.0,
|
|
108
|
+
near_band=0.02,
|
|
109
|
+
))
|
|
110
|
+
|
|
111
|
+
# Generate samples
|
|
112
|
+
samples = sampler.generate(
|
|
113
|
+
xyz=xyz, # Point cloud for distance computation
|
|
114
|
+
constraints=constraints, # From analyzer.analyze().constraints
|
|
115
|
+
strategy="inverse_square", # Sampling strategy
|
|
116
|
+
seed=42, # For reproducibility
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
# Export
|
|
120
|
+
sampler.export_parquet(samples, "output.parquet")
|
|
121
|
+
|
|
122
|
+
# Or get DataFrame
|
|
123
|
+
df = sampler.to_dataframe(samples)
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Constraint Types
|
|
127
|
+
|
|
128
|
+
The analyzer generates various constraint types:
|
|
129
|
+
|
|
130
|
+
- **BoxConstraint**: Axis-aligned bounding box
|
|
131
|
+
- **SphereConstraint**: Spherical region
|
|
132
|
+
- **SamplePointConstraint**: Direct point with signed distance
|
|
133
|
+
- **PocketConstraint**: Detected cavity region
|
|
134
|
+
|
|
135
|
+
Each constraint has:
|
|
136
|
+
- `sign`: "solid" (negative SDF) or "empty" (positive SDF)
|
|
137
|
+
- `weight`: Sample weight (default 1.0)
|
|
138
|
+
|
|
139
|
+
## Output Format
|
|
140
|
+
|
|
141
|
+
The exported parquet file contains columns:
|
|
142
|
+
|
|
143
|
+
| Column | Type | Description |
|
|
144
|
+
|--------|------|-------------|
|
|
145
|
+
| x, y, z | float | 3D position |
|
|
146
|
+
| phi | float | Signed distance (negative=solid, positive=empty) |
|
|
147
|
+
| nx, ny, nz | float | Normal vector (if available) |
|
|
148
|
+
| weight | float | Sample weight |
|
|
149
|
+
| source | string | Sample origin (e.g., "box_solid", "flood_fill_empty") |
|
|
150
|
+
| is_surface | bool | Whether sample is on surface |
|
|
151
|
+
| is_free | bool | Whether sample is in free space (EMPTY) |
|
|
152
|
+
|
|
153
|
+
## Configuration Options
|
|
154
|
+
|
|
155
|
+
### AnalyzerConfig
|
|
156
|
+
|
|
157
|
+
| Option | Default | Description |
|
|
158
|
+
|--------|---------|-------------|
|
|
159
|
+
| min_gap_size | 0.10 | Minimum gap size for flood fill (meters) |
|
|
160
|
+
| max_grid_dim | 200 | Maximum voxel grid dimension |
|
|
161
|
+
| cone_angle | 15.0 | Ray propagation cone half-angle (degrees) |
|
|
162
|
+
| normal_offset_pairs | 40 | Number of box pairs for normal_offset |
|
|
163
|
+
| idw_sample_count | 1000 | Total IDW samples |
|
|
164
|
+
| idw_max_distance | 0.5 | Maximum IDW distance (meters) |
|
|
165
|
+
| hull_filter_enabled | True | Filter outside X-Y alpha shape |
|
|
166
|
+
| hull_alpha | 1.0 | Alpha shape parameter |
|
|
167
|
+
|
|
168
|
+
### SamplerConfig
|
|
169
|
+
|
|
170
|
+
| Option | Default | Description |
|
|
171
|
+
|--------|---------|-------------|
|
|
172
|
+
| total_samples | 10000 | Default total samples |
|
|
173
|
+
| samples_per_primitive | 100 | Samples per constraint (CONSTANT) |
|
|
174
|
+
| samples_per_cubic_meter | 10000 | Sample density (DENSITY) |
|
|
175
|
+
| inverse_square_base_samples | 100 | Base samples (INVERSE_SQUARE) |
|
|
176
|
+
| inverse_square_falloff | 2.0 | Falloff exponent |
|
|
177
|
+
| near_band | 0.02 | Near-band width |
|
|
178
|
+
| seed | 0 | Random seed |
|
|
179
|
+
|
|
180
|
+
## License
|
|
181
|
+
|
|
182
|
+
MIT
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "sdf-sampler"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Auto-analysis and sampling of point clouds for SDF (Signed Distance Field) training data generation"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = { text = "MIT" }
|
|
7
|
+
authors = [{ name = "Liam", email = "liam@example.com" }]
|
|
8
|
+
requires-python = ">=3.11"
|
|
9
|
+
classifiers = [
|
|
10
|
+
"Development Status :: 4 - Beta",
|
|
11
|
+
"Intended Audience :: Developers",
|
|
12
|
+
"Intended Audience :: Science/Research",
|
|
13
|
+
"License :: OSI Approved :: MIT License",
|
|
14
|
+
"Programming Language :: Python :: 3",
|
|
15
|
+
"Programming Language :: Python :: 3.11",
|
|
16
|
+
"Programming Language :: Python :: 3.12",
|
|
17
|
+
"Topic :: Scientific/Engineering",
|
|
18
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
19
|
+
]
|
|
20
|
+
keywords = ["sdf", "signed-distance-field", "point-cloud", "sampling", "machine-learning"]
|
|
21
|
+
|
|
22
|
+
dependencies = [
|
|
23
|
+
"pydantic>=2.5.0",
|
|
24
|
+
"numpy>=1.26.0",
|
|
25
|
+
"pandas>=2.1.0",
|
|
26
|
+
"scipy>=1.11.0",
|
|
27
|
+
"alphashape>=1.3.1",
|
|
28
|
+
"pyarrow>=14.0.0",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
[project.optional-dependencies]
|
|
32
|
+
io = [
|
|
33
|
+
"trimesh>=4.0.0",
|
|
34
|
+
"laspy[laszip]>=2.5.0",
|
|
35
|
+
]
|
|
36
|
+
dev = [
|
|
37
|
+
"pytest>=8.0.0",
|
|
38
|
+
"pytest-asyncio>=0.23.0",
|
|
39
|
+
"pytest-cov>=4.0.0",
|
|
40
|
+
"ruff>=0.5.0",
|
|
41
|
+
"mypy>=1.8.0",
|
|
42
|
+
]
|
|
43
|
+
all = ["sdf-sampler[io,dev]"]
|
|
44
|
+
|
|
45
|
+
[project.urls]
|
|
46
|
+
Repository = "https://github.com/chiark/sdf-sampler"
|
|
47
|
+
|
|
48
|
+
[build-system]
|
|
49
|
+
requires = ["hatchling"]
|
|
50
|
+
build-backend = "hatchling.build"
|
|
51
|
+
|
|
52
|
+
[tool.hatch.build.targets.wheel]
|
|
53
|
+
packages = ["src/sdf_sampler"]
|
|
54
|
+
|
|
55
|
+
[tool.ruff]
|
|
56
|
+
line-length = 100
|
|
57
|
+
target-version = "py311"
|
|
58
|
+
|
|
59
|
+
[tool.ruff.lint]
|
|
60
|
+
select = ["E", "F", "I", "UP", "B", "SIM"]
|
|
61
|
+
ignore = ["E501"]
|
|
62
|
+
|
|
63
|
+
[tool.ruff.lint.isort]
|
|
64
|
+
known-first-party = ["sdf_sampler"]
|
|
65
|
+
|
|
66
|
+
[tool.pytest.ini_options]
|
|
67
|
+
testpaths = ["tests"]
|
|
68
|
+
asyncio_mode = "auto"
|
|
69
|
+
asyncio_default_fixture_loop_scope = "function"
|
|
70
|
+
|
|
71
|
+
[tool.mypy]
|
|
72
|
+
python_version = "3.11"
|
|
73
|
+
warn_return_any = true
|
|
74
|
+
warn_unused_configs = true
|
|
75
|
+
ignore_missing_imports = true
|