upxo.pxtal.twinned_simple_3d.cleaning_3d module
cleaning_3d.py
Post-twin voxel topology cleaning for the twinned simple 3D pipeline.
Both cleaning stages use fully vectorised local implementations that are
O(1) in grain count (single array pass), replacing the per-grain loop
approach in the generic gsdataops.grid_ops versions which scale
poorly on large domains.
- class upxo.pxtal.twinned_simple_3d.cleaning_3d.StructureCleaner3D(upscale_fallback: bool = False, split_jitter_deg: float = 0.0, rng_seed: int | None = None, min_clean_voxels: int = 0, do_spike_removal: bool = True, do_lobe_split: bool = True)[source]
Bases:
objectPost-twin voxel topology cleaner for the twinned simple 3D pipeline.
Stage 1 – Vertex spike removal (one global atomic pass, vectorised). Stage 2 – Lobe splitting via a single cc3d pass on the full array.
If defects persist and
upscale_fallbackis True the structure is doubled in each spatial dimension (~8x voxel count) and both stages are re-applied.- upscale_fallback
- split_jitter_deg
- lgi_clean: numpy.ndarray | None
- clean(lgi: numpy.ndarray, all_quats: Dict, twin_role: Dict, twin_parent_of: Dict)[source]
Run Stage 1 (spike removal) + Stage 2 (lobe splitting) on the post-twin grain structure using fast vectorised local methods.
Grains with fewer than
self.min_clean_voxelsvoxels are skipped in Stage 2. Set via themin_clean_voxelsconstructor argument.
- apply_upscale(lgi: numpy.ndarray, factor: int = 2) numpy.ndarray[source]
- jitter_report() str[source]
Report focused specifically on the orientation jitter applied to split-off lobes – split_events_report() covers every split event (parent, voxel size, jitter all together in one line); this isolates just the jitter part with summary statistics (min/max/ mean applied jitter), since that’s the piece someone tracking orientation-noise/meshing concerns wants on its own, not mixed in with lobe size/parent bookkeeping.
- classmethod clean_recursive(lgi: numpy.ndarray, all_quats: Dict, twin_role: Dict, twin_parent_of: Dict, n_passes: int = 1, upscale_fallback: bool = False, split_jitter_deg: float = 0.0, rng_seed: int | None = None, min_clean_voxels: int = 0, do_spike_removal: bool = True, do_lobe_split: bool = True, verbose: bool = True) Tuple[StructureCleaner3D, int][source]
Repeats
clean()up ton_passestimes, feeding each pass’s cleaned output (lgi_clean/all_quats_clean/twin_role_clean/ twin_parent_of_clean) into the next pass’s input, stopping early the moment a pass finds nothing left to fix (no spikes removed, no lobes split) – a single pass can occasionally leave a spike that only appears after that pass’s own lobe-splitting step, which a subsequent pass then catches.Constructs a fresh
StructureCleaner3Dfor every pass (same constructor arguments each time). The returned cleaner’sspike_count/n_splits/split_eventsare overwritten with the cumulative totals/merged history across every pass actually run, so callers see the full picture regardless of how many passes it took. Mergingsplit_eventsdicts across passes is safe – each pass’s lobe-splitting numbering starts from that pass’s own already-higher array max (it inherits the previous pass’s new grain IDs baked into the array), so passes never reuse the same new grain ID for a different lobe.- Parameters:
n_passes (int) – Maximum number of cleaning passes. 1 reproduces a single
clean()call exactly.parameters (Other)
- Returns:
(cleaner, n_passes_run) – cleaner : the final pass’s cleaner, with cumulative spike_count/n_splits/split_events across every pass run. n_passes_run : how many passes actually ran (<= n_passes; less if convergence was reached early).
- Return type: