Skip to content

Commit bcc12e3

Browse files
committed
[BUG] Refactor and fix voxel overlap handling and fault relations logic in dual_contouring module
1 parent 5aa9f20 commit bcc12e3

File tree

2 files changed

+111
-61
lines changed

2 files changed

+111
-61
lines changed
Lines changed: 109 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import List
1+
from typing import List, Dict, Optional
22

33
import numpy as np
44

@@ -8,120 +8,170 @@
88

99
def _apply_fault_relations_to_overlaps(
1010
all_meshes: List[DualContouringMesh],
11-
voxel_overlaps: dict,
11+
voxel_overlaps: Dict[str, dict],
1212
stacks_structure: StacksStructure
1313
) -> None:
1414
"""
1515
Apply fault relations to voxel overlaps by updating mesh vertices.
1616
1717
Args:
1818
all_meshes: List of dual contouring meshes
19-
faults_relations: Boolean matrix indicating fault relationships between stacks
2019
voxel_overlaps: Dictionary containing overlap information between stacks
21-
n_stacks: Total number of stacks
20+
stacks_structure: Structure containing fault relations and stack information
2221
"""
23-
faults_relations = stacks_structure.faults_relations
24-
n_stacks = stacks_structure.n_stacks
25-
number_surfaces_per_stack = stacks_structure.number_of_surfaces_per_stack_vector
26-
27-
if faults_relations is None:
22+
if stacks_structure.faults_relations is None:
2823
return
2924

30-
# Iterate through fault relations matrix
25+
faults_relations = stacks_structure.faults_relations
26+
n_stacks = stacks_structure.n_stacks
27+
surfaces_per_stack = stacks_structure.number_of_surfaces_per_stack_vector
28+
29+
# Process fault relations
30+
for origin_stack, destination_stack in _get_fault_pairs(faults_relations, n_stacks):
31+
surface_range = _get_surface_range(surfaces_per_stack, destination_stack)
32+
33+
for surface_n in surface_range:
34+
overlap_key = f"stack_{origin_stack}_vs_stack_{surface_n}"
35+
36+
if overlap_key in voxel_overlaps:
37+
_apply_vertex_sharing(
38+
all_meshes,
39+
origin_stack,
40+
surface_n,
41+
voxel_overlaps[overlap_key]
42+
)
43+
44+
45+
def _get_fault_pairs(faults_relations: np.ndarray, n_stacks: int):
46+
"""Generate pairs of stacks that have fault relations."""
3147
for origin_stack in range(n_stacks):
3248
for destination_stack in range(n_stacks):
3349
if faults_relations[origin_stack, destination_stack]:
34-
for surface_n in range(number_surfaces_per_stack[destination_stack], number_surfaces_per_stack[destination_stack + 1]):
35-
overlap_key = f"stack_{origin_stack}_vs_stack_{surface_n}"
50+
yield origin_stack, destination_stack
51+
3652

37-
# Check if there are actual overlaps between these stacks
38-
if overlap_key in voxel_overlaps:
39-
_apply_vertex_sharing(all_meshes, origin_stack, surface_n, voxel_overlaps[overlap_key])
53+
def _get_surface_range(surfaces_per_stack: np.ndarray, stack_index: int) -> range:
54+
"""Get the range of surfaces for a given stack."""
55+
return range(
56+
surfaces_per_stack[stack_index],
57+
surfaces_per_stack[stack_index + 1]
58+
)
4059

4160

4261
def _apply_vertex_sharing(
4362
all_meshes: List[DualContouringMesh],
44-
origin_mesh: int,
45-
destination_mesh: int,
63+
origin_mesh_idx: int,
64+
destination_mesh_idx: int,
4665
overlap_data: dict
4766
) -> None:
4867
"""
4968
Apply vertex sharing between origin and destination meshes based on overlap data.
5069
5170
Args:
5271
all_meshes: List of dual contouring meshes
53-
origin_mesh: Stack index that serves as the source of vertices
54-
destination_mesh: Stack index that receives vertices from origin
72+
origin_mesh_idx: Index of mesh that serves as the source of vertices
73+
destination_mesh_idx: Index of mesh that receives vertices from origin
5574
overlap_data: Dictionary containing indices and overlap information
56-
mesh_indices_offset: Starting mesh index for each stack
5775
"""
58-
# origin_mesh_idx = mesh_indices_offset[origin_stack]
59-
# destination_mesh_idx = mesh_indices_offset[destination_stack]
60-
origin_mesh_idx = origin_mesh
61-
destination_mesh_idx = destination_mesh
62-
63-
# Ensure mesh indices are valid
64-
if origin_mesh_idx >= len(all_meshes) or destination_mesh_idx >= len(all_meshes):
76+
if not _are_valid_mesh_indices(all_meshes, origin_mesh_idx, destination_mesh_idx):
6577
return
6678

67-
# Apply the vertex sharing (same logic as original _f function)
6879
origin_mesh = all_meshes[origin_mesh_idx]
6980
destination_mesh = all_meshes[destination_mesh_idx]
7081

71-
indices_in_origin = overlap_data["indices_in_stack_i"]
72-
indices_in_destination = overlap_data["indices_in_stack_j"]
82+
# Share vertices from origin to destination
83+
origin_indices = overlap_data["indices_in_stack_i"]
84+
destination_indices = overlap_data["indices_in_stack_j"]
85+
86+
destination_mesh.vertices[destination_indices] = origin_mesh.vertices[origin_indices]
87+
7388

74-
destination_mesh.vertices[indices_in_destination] = origin_mesh.vertices[indices_in_origin]
89+
def _are_valid_mesh_indices(all_meshes: List[DualContouringMesh], *indices: int) -> bool:
90+
"""Check if all provided mesh indices are valid."""
91+
return all(0 <= idx < len(all_meshes) for idx in indices)
7592

7693

77-
def find_repeated_voxels_across_stacks(all_left_right_codes: List[np.ndarray]) -> dict:
94+
def find_repeated_voxels_across_stacks(all_left_right_codes: List[np.ndarray]) -> Dict[str, dict]:
7895
"""
79-
Find repeated voxels using NumPy operations - better for very large arrays.
96+
Find repeated voxels using NumPy operations for efficient processing of large arrays.
8097
8198
Args:
8299
all_left_right_codes: List of left_right_codes arrays, one per stack
83100
84101
Returns:
85-
Dictionary with detailed overlap analysis
102+
Dictionary with detailed overlap analysis between stack pairs
86103
"""
87-
88104
if not all_left_right_codes:
89105
return {}
90106

91-
# Generate voxel codes for each stack
107+
stack_codes = _generate_voxel_codes(all_left_right_codes)
108+
return _find_overlaps_between_stacks(stack_codes, all_left_right_codes)
109+
92110

111+
def _generate_voxel_codes(all_left_right_codes: List[np.ndarray]) -> List[np.ndarray]:
112+
"""Generate voxel codes for each stack using packed bit directions."""
93113
from gempy_engine.modules.dual_contouring.fancy_triangulation import _StaticTriangulationData
114+
115+
pack_directions = _StaticTriangulationData.get_pack_directions_into_bits()
94116
stack_codes = []
117+
95118
for left_right_codes in all_left_right_codes:
96119
if left_right_codes.size > 0:
97-
voxel_codes = (left_right_codes * _StaticTriangulationData.get_pack_directions_into_bits()).sum(axis=1)
120+
voxel_codes = (left_right_codes * pack_directions).sum(axis=1)
98121
stack_codes.append(voxel_codes)
99122
else:
100123
stack_codes.append(np.array([]))
124+
125+
return stack_codes
101126

102-
overlaps = {}
103127

104-
# Check each pair of stacks
128+
def _find_overlaps_between_stacks(
129+
stack_codes: List[np.ndarray],
130+
all_left_right_codes: List[np.ndarray]
131+
) -> Dict[str, dict]:
132+
"""Find overlaps between all pairs of stacks."""
133+
overlaps = {}
134+
105135
for i in range(len(stack_codes)):
106136
for j in range(i + 1, len(stack_codes)):
107-
if stack_codes[i].size == 0 or stack_codes[j].size == 0:
108-
continue
109-
110-
# Find common voxel codes using numpy
111-
common_codes = np.intersect1d(stack_codes[i], stack_codes[j])
112-
113-
if len(common_codes) > 0:
114-
# Get indices of common voxels in each stack
115-
indices_i = np.isin(stack_codes[i], common_codes)
116-
indices_j = np.isin(stack_codes[j], common_codes)
117-
118-
overlaps[f"stack_{i}_vs_stack_{j}"] = {
119-
'common_voxel_codes' : common_codes,
120-
'count' : len(common_codes),
121-
'indices_in_stack_i' : np.where(indices_i)[0],
122-
'indices_in_stack_j' : np.where(indices_j)[0],
123-
'common_binary_codes_i': all_left_right_codes[i][indices_i],
124-
'common_binary_codes_j': all_left_right_codes[j][indices_j]
125-
}
126-
137+
overlap_data = _process_stack_pair(
138+
stack_codes[i], stack_codes[j],
139+
all_left_right_codes[i], all_left_right_codes[j],
140+
i, j
141+
)
142+
143+
if overlap_data:
144+
overlaps[f"stack_{i}_vs_stack_{j}"] = overlap_data
145+
127146
return overlaps
147+
148+
149+
def _process_stack_pair(
150+
codes_i: np.ndarray,
151+
codes_j: np.ndarray,
152+
left_right_i: np.ndarray,
153+
left_right_j: np.ndarray,
154+
stack_i: int,
155+
stack_j: int
156+
) -> Optional[dict]:
157+
"""Process a pair of stacks to find overlapping voxels."""
158+
if codes_i.size == 0 or codes_j.size == 0:
159+
return None
160+
161+
common_codes = np.intersect1d(codes_i, codes_j)
162+
163+
if len(common_codes) == 0:
164+
return None
165+
166+
# Find indices of common voxels in each stack
167+
indices_i = np.isin(codes_i, common_codes)
168+
indices_j = np.isin(codes_j, common_codes)
169+
170+
return {
171+
'common_voxel_codes': common_codes,
172+
'count': len(common_codes),
173+
'indices_in_stack_i': np.where(indices_i)[0],
174+
'indices_in_stack_j': np.where(indices_j)[0],
175+
'common_binary_codes_i': left_right_i[indices_i],
176+
'common_binary_codes_j': left_right_j[indices_j]
177+
}

tests/test_common/test_modules/test_dual_II.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,15 @@
99
from tests.conftest import plot_pyvista
1010

1111

12-
def test_dual_contouring_on_fault_model(one_fault_model, n_oct_levels=4):
12+
def test_dual_contouring_on_fault_model(one_fault_model, n_oct_levels=5):
1313
interpolation_input: InterpolationInput
1414
structure: InputDataDescriptor
1515
options: InterpolationOptions
1616

1717
interpolation_input, structure, options = one_fault_model
1818

1919
import numpy as np
20-
interpolation_input.surface_points.sp_coords[:, 2] += np.random.uniform(-0.1, 0.1, interpolation_input.surface_points.sp_coords[:, 2].shape)
20+
interpolation_input.surface_points.sp_coords[:, 2] += np.random.uniform(-0.02, 0.02, interpolation_input.surface_points.sp_coords[:, 2].shape)
2121
options.compute_scalar_gradient = False
2222
options.evaluation_options.dual_contouring = True
2323
options.evaluation_options.mesh_extraction_masking_options = MeshExtractionMaskingOptions.INTERSECT

0 commit comments

Comments
 (0)