Workflows
This page shows complete, annotated code examples for common UPXO tasks.
All examples follow patterns taken directly from the demo notebooks in
src/upxo/demos/.
Note
UPXO reads simulation parameters from an Excel dashboard file
(input_dashboard.xls). The examples below use a placeholder path — replace
it with the path to your own dashboard file. Template dashboards are provided
under src/upxo/interfaces/user_inputs/.
Workflow 1 — MCGS2D: Simulate, Detect Grains, Characterise
This is the standard MCGS2D pipeline, equivalent to what gschar1.ipynb demonstrates.
from upxo.ggrowth.mcgs import mcgs
# Step 1 — Load dashboard and run the MC simulation
pxt = mcgs(input_dashboard='path/to/input_dashboard.xls')
pxt.simulate()
# Step 2 — Detect grains at every saved time slice
pxt.detect_grains()
# Step 3 — Pick a time slice
# pxt.m is the list of saved MCS step indices
tslice = pxt.m[-1] # last saved step
gs = pxt.gs[tslice] # mcgs2_grain_structure object
# Step 4 — Characterise: request exactly the properties you need
gs.char_morph_2d(
use_version=2,
npixels=True,
area=True,
aspect_ratio=True,
solidity=True,
circularity=True,
char_gb=False,
make_skim_prop=True,
get_grain_coords=True,
)
# Step 5 — Inspect results
print(f"Number of grains: {gs.n}")
print(gs.prop.columns.tolist()) # shows which columns were computed
print(gs.prop.head())
Properties are only present in gs.prop if the matching flag was set to True
in the char_morph_2d call. Available flags include: npixels, area,
aspect_ratio, solidity, circularity, eccentricity,
major_axis_length, minor_axis_length, perimeter, eq_diameter,
compactness, morph_ori, euler_number.
Workflow 2 — Visualise the Labelled Grain Image
After detect_grains(), the labelled grain image is stored in gs.lgi.
import matplotlib.pyplot as plt
tslice = pxt.m[-1]
gs = pxt.gs[tslice]
plt.figure()
plt.imshow(gs.lgi, cmap='tab20')
plt.colorbar(label='Grain ID')
plt.title(f'MCGS2D — tslice {tslice}, {gs.n} grains')
plt.axis('off')
plt.tight_layout()
plt.show()
To visualise the raw MC spin state (before grain detection):
plt.imshow(pxt.S, cmap='nipy_spectral')
plt.title('MC spin state (final)')
plt.show()
Workflow 3 — Grain Size Distribution
Request npixels=True (pixel count per grain) when calling char_morph_2d,
then plot the distribution.
import matplotlib.pyplot as plt
gs.char_morph_2d(use_version=2, npixels=True, make_skim_prop=True)
pixel_counts = gs.prop['npixels'].values
plt.figure()
plt.hist(pixel_counts, bins=20, edgecolor='k')
plt.xlabel('Grain size (pixels)')
plt.ylabel('Count')
plt.title('Grain size distribution')
plt.tight_layout()
plt.show()
Workflow 4 — Grain Neighbourhood
tslice = pxt.m[-1]
gs = pxt.gs[tslice]
# Characterise first so bounding boxes exist for the neighbour search
gs.char_morph_2d(use_version=2, bbox=True, bbox_ex=True, make_skim_prop=True)
# Compute neighbours for every grain
gs.find_neigh(include_central_grain=False, print_msg=True, use_numba=True)
# Neighbours of grain with ID 10
print(gs.neigh_gid[10])
Note
A known bug in some builds causes the central grain to appear in its own
neighbour list when include_central_grain=False. The workaround used in
the demo notebooks is:
for gid in gs.neigh_gid.keys():
if gid in gs.neigh_gid[gid]:
gs.neigh_gid[gid].remove(gid)
Workflow 5 — Finding Small Grains and Boundary Grains
Use the gid_ops module to query the labelled image directly.
import upxo.gsdataops.gid_ops as gidOps
lfi = gs.lgi
# Grains with 5 pixels or fewer
small_grains = gidOps.find_small_fids(lfi, threshold=5)
print("Small grain IDs:", small_grains)
# Grains whose pixels touch the domain boundary
boundary_grains = gidOps.find_boundary_fids2d(lfi)
print("Boundary grain IDs:", boundary_grains)
Workflow 6 — Resampling and Rescaling the Grid
Use grid_ops to change the resolution of the state array or labelled image.
from upxo.gsdataops.grid_ops import resample_grid_2d, rescale_grid_2d
# Downsample by factor 0.25 using the simulation's own grid object
resampled, x_new, y_new, xinc_new, yinc_new = resample_grid_2d(
pxt.S, pxt.uigrid, sf=0.25, method='nearest'
)
print("Original shape:", pxt.S.shape)
print("Resampled shape:", resampled.shape)
# Rescale to twice the resolution
scaled = rescale_grid_2d(pxt.S, scale_factor=2, method='nearest')
print("Scaled shape:", scaled.shape)
Workflow 7 — Merging Small Grains
Single-pixel or sub-threshold grains can be absorbed into their largest neighbour before downstream analysis.
import numpy as np
from upxo.pxtalops.gssmooth2d import _merge_small_grains
lfi = gs.lgi
lfi_clean = _merge_small_grains(lfi, area_threshold=3)
print("Unique grains before:", len(np.unique(lfi)))
print("Unique grains after :", len(np.unique(lfi_clean)))
Workflow 8 — Comparing Grain Size Across Time Slices
Iterate over saved time slices to track how mean grain size evolves.
import numpy as np
import matplotlib.pyplot as plt
mean_sizes = []
for tslice in pxt.m:
gs = pxt.gs[tslice]
gs.char_morph_2d(use_version=2, npixels=True, make_skim_prop=True)
mean_sizes.append(gs.prop['npixels'].mean())
plt.figure()
plt.plot(pxt.m, mean_sizes, marker='o')
plt.xlabel('Monte-Carlo step (tslice)')
plt.ylabel('Mean grain size (pixels)')
plt.title('Grain growth kinetics')
plt.tight_layout()
plt.show()
Next Steps
Key Concepts — understand the data model behind these examples
API Reference — full module and class documentation
Grain Characterisation wiki — extended characterisation workflows
Visualisation wiki — 2D and 3D plotting
Meshing wiki — FE mesh generation