upxo.pxtal.fm_steel_3d.with_orientations_3d module

FMSteel3DWithOrientations: Final state class with full FM steel hierarchy and orientations.

This module contains the complete FM steel microstructure class with all hierarchical levels (grains → PAGs → packets → blocks) and crystal orientations assigned to blocks via Kurdjumov-Sachs relationship.

Classes:

FMSteel3DWithOrientations: Final complete FM steel state class.

class upxo.pxtal.fm_steel_3d.with_orientations_3d.FMSteel3DWithOrientations(parent, grain_orientations: Dict[int, Tuple[float, float, float]], block_orientations: Dict[str, Tuple[float, float, float]], pag_orientations: Dict[int, Tuple[float, float, float]] | None = None, block_to_variant_idx: Dict[str, int] | None = None, random_seed: int | None = None, verbosity: int | None = None, log_sink=None)[source]

Bases: object

Complete 3D FM steel microstructure with full hierarchy and orientations.

This is the final state in the FM steel generation pipeline. It contains: - Base grain structure - PAG partitioning - Block hierarchy within each PAG - BCC crystal orientations assigned to blocks

This state is ready for mesh generation, visualization, or export.

_parent

Reference to parent block structure (read-only complete structure).

Type:

FMSteel3DWithBlocks

grain_orientations

Maps grain ID → BCC Euler angles. Format: {grain_id: (phi1_deg, Phi_deg, phi2_deg)}.

Type:

dict[int, tuple[float, float, float]]

block_orientations

Maps block_id → BCC Euler angles. Format: {block_id: (phi1_deg, Phi_deg, phi2_deg)}.

Type:

dict[str, tuple[float, float, float]]

pag_orientations

Maps PAG ID → FCC (parent austenite) Euler angles. Format: {pag_id: (phi1_deg, Phi_deg, phi2_deg)}.

Type:

dict[int, tuple[float, float, float]]

_random_seed

Random seed used for orientation assignment.

Type:

int

grain_orientations
block_orientations
block_to_variant_idx
pag_orientations
property lgi: numpy.ndarray

Labeled grain image from base.

property grain_locs: Dict[int, numpy.ndarray]

Grain voxel coordinates from base.

property clusters_dict: Dict[int, List[int]]

PAG clustering.

property all_blocks: Dict[str, numpy.ndarray]

Block voxel data.

property grain_to_blocks_map: Dict[int, List[str]]

grain_id -> [block_id, …] mapping (keys are grain_ids = packet ids).

property grain_to_pag_id: Dict[int, int]

Reverse lookup grain_id -> pag_id.

property grain_to_local_pkt_idx: Dict[int, int]

grain_id -> local_idx.

Type:

1-based local packet ordinal within each PAG

property grain_to_plane_idx: Dict[int, int]

grain_id -> {111}FCC habit-plane index (0-3) from parent block level.

property block_slicing_normals: Dict[str, numpy.ndarray]

block_id -> unit normal of the {111}FCC habit plane used to slice it, from parent block level (see FMSteel3DWithBlocks docstring).

property n_grains: int

Total grains.

property n_blocks: int

Total blocks.

property n_pags: int

Total PAGs.

property physical_dimensions

Physical domain size.

property voxel_size: float

Voxel size.

property units: str

Physical unit string (‘microns’, ‘mm’, ‘m’).

property isolated_grains

Isolated grains from parent PAG level.

property retained_austenite_pag_ids

Retained-austenite PAG IDs from parent PAG level (see FMSteel3DWithPAGs docstring).

ensure_isolated_grain_orientations(random_seed: int | None = None) → None[source]

See FMSteel3DWithPAGs.ensure_isolated_grain_orientations.

get_isolated_grain_orientation(gid: int) → Tuple[float, float, float] | None[source]

See FMSteel3DWithPAGs.get_isolated_grain_orientation.

get_full_hierarchy_statistics() → Dict[str, any][source]

Compute comprehensive statistics across all hierarchy levels.

Returns:

Keys include: - n_grains, grain_voxel_stats - n_pags, pags_grains_stats - n_blocks, block_voxel_stats - n_isolated_grains - total_voxels_in_fm_structure

Return type:

dict

compute_misorientation_distribution(n_sample_pairs: int = 5000, random_seed: int | None = None) → Dict[str, numpy.ndarray][source]

Compute cubic-symmetry-reduced misorientation angles between block pairs.

Samples n_sample_pairs pairs in each category (within-PAG and across-PAG) and returns the disorientation angle (minimum over all 24 cubic symmetry operators).

Within-PAG distribution should show peaks at the KS-predicted angles (~10.5°, ~14.9°, ~20.6°, ~47.1°, ~60°). Across-PAG distribution should be roughly uniform (no preferred angle).

Parameters:
  • n_sample_pairs (int, optional) – Target number of sampled pairs per category. Default 5000.

  • random_seed (int, optional) – Random seed for reproducibility.

Returns:

‘within_pag’ : ndarray of disorientation angles in degrees ‘across_pag’ : ndarray of disorientation angles in degrees

Return type:

dict

get_ks_variant_statistics() → Dict[source]

How evenly the 6 KS variants within each packet were actually used.

For every (pag_id, plane_idx) packet with 2+ blocks, tallies how many blocks received each variant index that was actually assigned, then summarises that per-packet distribution with the same size_balance_metrics (cv, min_max_ratio, gini) used elsewhere for packet_size_balance – perfectly even usage across whichever variants were used gives cv=0, min_max_ratio=1, gini=0; a packet where every block ended up sharing one variant (the worst case the adjacency- aware graph-colouring in assign_orientations_to_all_blocks tries to avoid) sits at the opposite extreme.

Requires block_to_variant_idx, which is only populated when orientations were assigned via assign_orientations_to_all_blocks (i.e. FMSteel3DWithBlocks.assign_orientations()) – empty if custom block orientations were injected instead.

available_phases() → List[int][source]

Phase ids actually present in this structure, for populating a phase-selector dropdown (e.g. the Block-Level IPF Map panel).

PHASE_MARTENSITE is present whenever any blocks exist; PHASE_ RETAINED_AUSTENITE is present whenever isolated_grains is non-empty (the flattened view covering both PAG-covered and leftover-isolated retained-austenite grains – see FMSteel3DWithPAGs docstring).

get_phase_voxels_and_orientations(phase_id: int) → Tuple[Dict, Dict[int, Tuple[float, float, float]]][source]

Feature voxels + orientations for one phase, for phase-filtered IPF maps (see viz/orientation_viz_3d.py and gui/pages_viz.py).

PHASE_MARTENSITE -> block granularity: (all_blocks, block_orientations), exactly what the existing Block-Level IPF Map already renders (blocks only ever exist for transformed PAGs, so no filtering is needed).

PHASE_RETAINED_AUSTENITE -> grain granularity (retained austenite is never split into packets/blocks): every retained-austenite grain keyed by grain_id, orientation from either its retained PAG or its own isolated_grain_orientations entry.

Returns:

(features, orientations) – features: {feature_id: (n_voxels, 3) voxel coordinate array} orientations: {feature_id: (phi1, Phi, phi2) Bunge-Euler degrees}

Return type:

(dict, dict)

get_misorientation_statistics() → Dict[str, any][source]

Compute grain-grain and block-block misorientation statistics.

Uses cubic_misorientation_old1 (from parent class) to compute misorientation angles between neighboring grains/blocks.

Returns:

Keys: ‘grain_grain_misori’, ‘block_block_misori’, (each is dict of statistics: min, max, mean, etc.)

Return type:

dict

build_euler_angle_3d_maps(level: str = 'block') → Tuple[numpy.ndarray, numpy.ndarray, numpy.ndarray][source]

Build dense 3D Euler angle maps (phi1, Phi, phi2) over the full RVE.

Each voxel receives the Bunge ZXZ Euler angles (degrees) of its parent block. Voxels not covered by any block (PAG boundaries, isolated grains) retain the zero initialisation.

Parameters:

level (str, optional) – Hierarchy level to draw orientations from. Currently only 'block' is supported. Default 'block'.

Returns:

Three float32 arrays, each shaped like self.lgi. Angles are in degrees (Bunge ZXZ convention).

Return type:

tuple of (phi1_3d, Phi_3d, phi2_3d)

plot_gs_pvvox(alpha: float = 1.0, title: str = 'FM Steel 3D', **kwargs)[source]

Visualize grain structure (PyVista voxels).

plot_pag_map_pyvista(gids_to_plot=None, **kwargs)[source]

Visualize PAG map.

plot_ipf_map_pyvista_v2(gids_to_plot=None, **kwargs)[source]

Visualize IPF map.

visualize_block_morphology(**kwargs)[source]

Visualize all blocks with random colors.

visualize_block_morphology_v1(**kwargs)[source]

Visualize all blocks with color bar.

visualize_block_ipf_map(**kwargs)[source]

Visualize blocks colored by IPF.

plot_pag_ks_verification_v1(pag_id: int, **kwargs)[source]

Verify KS relationship for a PAG.

plot_distribution(data: numpy.ndarray, title: str, xlabel: str, **kwargs)[source]

Plot histogram.

generate_subblocks(subblock_thickness_range_um: Tuple[float, float] = (0.5, 1.5), intrablock_ori_spread_deg: float = 2.0, thin_block_strategy: str = 'skip', random_seed: int | None = None, subblock_slab_connectivity: int = 26) → FMSteel3DWithSubBlocks[source]

Subdivide every block into sub-blocks (laths) with per-sub-block orientations.

Sub-block slicing reuses each block’s inherited slicing normal, so laths are parallel to the parent block’s habit plane. Orientations are the parent block orientation plus a small random axis-angle perturbation within ±intrablock_ori_spread_deg.

Parameters:
  • subblock_thickness_range_um (tuple of (float, float)) – (min, max) sub-block thickness in the same physical units as LX/LY/LZ (typically µm). Converted to voxels using the stored voxel_size.

  • intrablock_ori_spread_deg (float, optional) – Half-width of orientation spread around parent block (degrees). Default 2.0.

  • thin_block_strategy (str, optional) – Policy for blocks too thin to subdivide: ‘skip’ keeps the block whole. Default ‘skip’.

  • random_seed (int, optional) – Seed for reproducibility.

Return type:

FMSteel3DWithSubBlocks

to_dict() → Dict[source]

Serialize full hierarchy to dict.