sdf-sampler 0.1.0__py3-none-any.whl

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.
@@ -0,0 +1,85 @@
1
+ # ABOUTME: Auto-analysis result models
2
+ # ABOUTME: Defines algorithm types and analysis result structures
3
+
4
+ from datetime import UTC, datetime
5
+ from enum import Enum
6
+ from typing import Any
7
+
8
+ from pydantic import BaseModel, ConfigDict, Field
9
+
10
+
11
+ class AlgorithmType(str, Enum):
12
+ """Available auto-analysis algorithms."""
13
+
14
+ POCKET = "pocket" # Cavity detection via voxel flood-fill
15
+ NORMAL_OFFSET = "normal_offset" # Surface-relative offset regions
16
+ FLOOD_FILL = "flood_fill" # Exterior flood fill from sky for EMPTY regions
17
+ VOXEL_REGIONS = "voxel_regions" # Underground regions via voxel classification for SOLID boxes
18
+ NORMAL_IDW = "normal_idw" # Inverse distance weighted normal sampling
19
+
20
+
21
+ ALL_ALGORITHMS = [
22
+ AlgorithmType.POCKET,
23
+ AlgorithmType.NORMAL_OFFSET,
24
+ AlgorithmType.FLOOD_FILL,
25
+ AlgorithmType.VOXEL_REGIONS,
26
+ AlgorithmType.NORMAL_IDW,
27
+ ]
28
+
29
+
30
+ class GeneratedConstraint(BaseModel):
31
+ """A constraint generated by auto-analysis.
32
+
33
+ Wraps standard constraint data with algorithm metadata.
34
+ """
35
+
36
+ constraint: dict[str, Any] = Field(..., description="Constraint data (type, sign, geometry)")
37
+ algorithm: AlgorithmType = Field(..., description="Algorithm that generated this")
38
+ confidence: float = Field(
39
+ ..., ge=0.0, le=1.0, description="Algorithm's confidence in this constraint"
40
+ )
41
+ description: str = Field(..., description="Human-readable description")
42
+
43
+
44
+ class AlgorithmStats(BaseModel):
45
+ """Statistics for a single algorithm's contribution."""
46
+
47
+ constraints_generated: int = Field(0, description="Number of constraints from this algorithm")
48
+ coverage_description: str = Field("", description="What this algorithm detected")
49
+
50
+
51
+ class AnalysisSummary(BaseModel):
52
+ """Summary statistics for auto-analysis results."""
53
+
54
+ total_constraints: int = Field(..., description="Total constraints generated")
55
+ solid_constraints: int = Field(..., description="Constraints marking solid regions")
56
+ empty_constraints: int = Field(..., description="Constraints marking empty regions")
57
+ algorithms_contributing: int = Field(..., description="Number of algorithms that contributed")
58
+
59
+
60
+ class AnalysisResult(BaseModel):
61
+ """Complete auto-analysis result with generated constraints.
62
+
63
+ This is the primary return type from SDFAnalyzer.analyze().
64
+ """
65
+
66
+ model_config = ConfigDict(ser_json_timedelta="iso8601")
67
+
68
+ analysis_id: str = Field(..., description="Unique identifier for this analysis")
69
+ computed_at: datetime = Field(
70
+ default_factory=lambda: datetime.now(UTC),
71
+ description="When analysis was performed",
72
+ )
73
+ algorithms_run: list[str] = Field(..., description="Algorithms that were executed")
74
+ summary: AnalysisSummary = Field(..., description="Aggregated statistics")
75
+ algorithm_stats: dict[str, AlgorithmStats] = Field(
76
+ default_factory=dict, description="Per-algorithm statistics"
77
+ )
78
+ generated_constraints: list[GeneratedConstraint] = Field(
79
+ default_factory=list, description="Constraints ready for use"
80
+ )
81
+
82
+ @property
83
+ def constraints(self) -> list[dict[str, Any]]:
84
+ """Get just the constraint dicts for convenience."""
85
+ return [gc.constraint for gc in self.generated_constraints]
@@ -0,0 +1,192 @@
1
+ # ABOUTME: Constraint models for SDF region marking
2
+ # ABOUTME: Defines geometric primitives and regions for inside/outside labeling
3
+
4
+ import uuid
5
+ from enum import Enum
6
+ from typing import Annotated, Literal
7
+
8
+ from pydantic import BaseModel, Field
9
+
10
+
11
+ class SignConvention(str, Enum):
12
+ """Sign convention for SDF values.
13
+
14
+ User-friendly terminology:
15
+ - SOLID = negative SDF (inside material)
16
+ - EMPTY = positive SDF (outside, free space)
17
+ - SURFACE = zero SDF (on the boundary)
18
+ """
19
+
20
+ SOLID = "solid" # Inside / negative SDF
21
+ EMPTY = "empty" # Outside / positive SDF
22
+ SURFACE = "surface" # On boundary / zero SDF
23
+
24
+
25
+ class BaseConstraint(BaseModel):
26
+ """Base class for all constraint types."""
27
+
28
+ id: str = Field(default_factory=lambda: str(uuid.uuid4()))
29
+ name: str | None = Field(default=None, description="Optional user-friendly name")
30
+ sign: SignConvention = Field(..., description="Label: solid (inside) or empty (outside)")
31
+ weight: float = Field(default=1.0, ge=0.0, le=10.0, description="Sample weight")
32
+
33
+
34
+ class BoxConstraint(BaseConstraint):
35
+ """Axis-aligned bounding box constraint."""
36
+
37
+ type: Literal["box"] = "box"
38
+ center: tuple[float, float, float] = Field(..., description="Box center (x, y, z)")
39
+ half_extents: tuple[float, float, float] = Field(
40
+ ..., description="Half-extents in each dimension"
41
+ )
42
+
43
+
44
+ class SphereConstraint(BaseConstraint):
45
+ """Spherical region constraint."""
46
+
47
+ type: Literal["sphere"] = "sphere"
48
+ center: tuple[float, float, float] = Field(..., description="Sphere center (x, y, z)")
49
+ radius: float = Field(..., gt=0, description="Sphere radius")
50
+
51
+
52
+ class HalfspaceConstraint(BaseConstraint):
53
+ """Half-space (infinite plane) constraint.
54
+
55
+ Defines a plane and which side is labeled.
56
+ The normal points toward the EMPTY (outside) side by convention.
57
+ """
58
+
59
+ type: Literal["halfspace"] = "halfspace"
60
+ point: tuple[float, float, float] = Field(..., description="A point on the plane")
61
+ normal: tuple[float, float, float] = Field(
62
+ ..., description="Outward normal (points toward empty/outside)"
63
+ )
64
+
65
+
66
+ class CylinderConstraint(BaseConstraint):
67
+ """Cylindrical region constraint."""
68
+
69
+ type: Literal["cylinder"] = "cylinder"
70
+ center: tuple[float, float, float] = Field(..., description="Center of cylinder base")
71
+ axis: tuple[float, float, float] = Field(default=(0, 0, 1), description="Cylinder axis")
72
+ radius: float = Field(..., gt=0, description="Cylinder radius")
73
+ height: float = Field(..., gt=0, description="Cylinder height")
74
+
75
+
76
+ class BrushStrokeConstraint(BaseConstraint):
77
+ """User-painted volumetric stroke in 3D space."""
78
+
79
+ type: Literal["brush_stroke"] = "brush_stroke"
80
+ stroke_points: list[tuple[float, float, float]] = Field(
81
+ ..., description="Path of brush center positions"
82
+ )
83
+ radius: float = Field(..., gt=0, description="Brush/stroke radius")
84
+
85
+
86
+ class SeedPropagationConstraint(BaseConstraint):
87
+ """Seed point with spatial propagation."""
88
+
89
+ type: Literal["seed_propagation"] = "seed_propagation"
90
+ seed_point: tuple[float, float, float] = Field(..., description="Seed location")
91
+ seed_index: int | None = Field(default=None, description="Nearest point index")
92
+ propagation_radius: float = Field(..., gt=0, description="Max propagation distance")
93
+ propagation_method: Literal["euclidean", "geodesic"] = Field(
94
+ default="euclidean", description="Distance metric for propagation"
95
+ )
96
+ propagated_indices: list[int] = Field(
97
+ default_factory=list, description="Points reached by propagation"
98
+ )
99
+ confidences: list[float] = Field(
100
+ default_factory=list, description="Confidence per propagated point"
101
+ )
102
+
103
+
104
+ class RayInfo(BaseModel):
105
+ """Single ray from a ray-scribble interaction."""
106
+
107
+ origin: tuple[float, float, float] = Field(..., description="Ray origin (camera position)")
108
+ direction: tuple[float, float, float] = Field(..., description="Normalized ray direction")
109
+ hit_distance: float = Field(..., gt=0, description="Distance to first point cloud hit")
110
+ surface_normal: tuple[float, float, float] | None = Field(
111
+ default=None, description="Surface normal at hit point if available"
112
+ )
113
+ hit_point_index: int | None = Field(
114
+ default=None, description="Index of hit point in consolidated positions"
115
+ )
116
+ local_spacing: float | None = Field(
117
+ default=None, description="Local point spacing at hit point (k-NN mean distance)"
118
+ )
119
+
120
+
121
+ class RayCarveConstraint(BaseConstraint):
122
+ """Constraint from ray-scribble interaction.
123
+
124
+ Each ray defines:
125
+ - EMPTY samples along ray from origin to (hit_distance - empty_band)
126
+ - SURFACE samples near hit_distance (from -surface_band to +back_buffer)
127
+ """
128
+
129
+ type: Literal["ray_carve"] = "ray_carve"
130
+ rays: list[RayInfo] = Field(..., description="Rays cast during scribble stroke")
131
+ empty_band_width: float = Field(
132
+ default=0.1, gt=0, description="Distance before hit to sample as EMPTY"
133
+ )
134
+ surface_band_width: float = Field(
135
+ default=0.02, gt=0, description="Distance before hit for SURFACE samples"
136
+ )
137
+ back_buffer_width: float = Field(
138
+ default=0.0, ge=0, description="Fixed distance past hit (fallback when no local_spacing)"
139
+ )
140
+ back_buffer_coefficient: float = Field(
141
+ default=1.0, ge=0, description="Multiplier for per-ray local_spacing"
142
+ )
143
+
144
+
145
+ class PocketConstraint(BaseConstraint):
146
+ """Pocket (cavity) constraint from voxel analysis."""
147
+
148
+ type: Literal["pocket"] = "pocket"
149
+ pocket_id: int = Field(..., description="Pocket identifier from analysis")
150
+ voxel_count: int = Field(..., description="Number of voxels in pocket")
151
+ centroid: tuple[float, float, float] = Field(..., description="Pocket centroid")
152
+ bounds_low: tuple[float, float, float] = Field(..., description="Pocket AABB min")
153
+ bounds_high: tuple[float, float, float] = Field(..., description="Pocket AABB max")
154
+ volume_estimate: float = Field(..., description="Estimated volume in world units")
155
+
156
+
157
+ class SliceSelectionConstraint(BaseConstraint):
158
+ """Constraint from 2D slice selection."""
159
+
160
+ type: Literal["slice_selection"] = "slice_selection"
161
+ point_indices: list[int] = Field(..., description="Selected point indices")
162
+ slice_plane: Literal["xy", "xz", "yz"] = Field(..., description="Which plane was used")
163
+ slice_position: float = Field(..., description="Position along perpendicular axis")
164
+
165
+
166
+ class SamplePointConstraint(BaseConstraint):
167
+ """Single point sample constraint from IDW normal sampling.
168
+
169
+ Direct training samples at specific 3D positions with known signed distance.
170
+ """
171
+
172
+ type: Literal["sample_point"] = "sample_point"
173
+ position: tuple[float, float, float] = Field(..., description="Sample position (x, y, z)")
174
+ distance: float = Field(
175
+ ..., description="Signed distance to surface (negative=solid, positive=empty)"
176
+ )
177
+
178
+
179
+ # Union type for all constraints
180
+ Constraint = Annotated[
181
+ BoxConstraint
182
+ | SphereConstraint
183
+ | HalfspaceConstraint
184
+ | CylinderConstraint
185
+ | BrushStrokeConstraint
186
+ | SeedPropagationConstraint
187
+ | RayCarveConstraint
188
+ | PocketConstraint
189
+ | SliceSelectionConstraint
190
+ | SamplePointConstraint,
191
+ Field(discriminator="type"),
192
+ ]
@@ -0,0 +1,49 @@
1
+ # ABOUTME: Training sample models for SDF data generation
2
+ # ABOUTME: Defines sample formats and sampling strategies
3
+
4
+ from enum import Enum
5
+
6
+ from pydantic import BaseModel, Field
7
+
8
+
9
+ class SamplingStrategy(str, Enum):
10
+ """Sampling strategy for generating training samples from constraints."""
11
+
12
+ CONSTANT = "constant" # Fixed samples per constraint
13
+ DENSITY = "density" # Samples proportional to constraint volume
14
+ INVERSE_SQUARE = "inverse_square" # More samples near surface, fewer far away
15
+
16
+
17
+ class TrainingSample(BaseModel):
18
+ """Single training sample with SDF value.
19
+
20
+ This is the core output format for SDF training data.
21
+ """
22
+
23
+ x: float
24
+ y: float
25
+ z: float
26
+ phi: float = Field(..., description="Signed distance value")
27
+ nx: float | None = None
28
+ ny: float | None = None
29
+ nz: float | None = None
30
+ weight: float = 1.0
31
+ source: str = Field(..., description="Sample source (e.g., 'surface_anchor', 'near_band')")
32
+ is_surface: bool = False
33
+ is_free: bool = False
34
+
35
+ def to_dict(self) -> dict:
36
+ """Convert to dictionary for DataFrame construction."""
37
+ return {
38
+ "x": self.x,
39
+ "y": self.y,
40
+ "z": self.z,
41
+ "phi": self.phi,
42
+ "nx": self.nx,
43
+ "ny": self.ny,
44
+ "nz": self.nz,
45
+ "weight": self.weight,
46
+ "source": self.source,
47
+ "is_surface": self.is_surface,
48
+ "is_free": self.is_free,
49
+ }