upxo.viz.vizDistr module

vizDistr.py — Distribution visualisation for UPXO grain structure analyses.

Provides the DistrViz class for plotting scalar grain property distributions (area, perimeter, aspect ratio, …) and angular misorientation distributions (MDF). Designed to complement ebsdviz.plot_mdf — use DistrViz.plot_mdf when peaks are not yet computed; use ebsdviz.plot_mdf for fully annotated MDF with peak labels and KDE from the peaks dict.

Typical usage

Grain size:

dv = DistrViz(areas, label=’Grain area’, units=’µm²’) fig, ax = dv.plot_hist(bins=40, show_kde=True, step_size=rdr.step_size) plt.show() dv.print_stats()

MDF (lightweight, no peaks dict required):

dv = DistrViz.from_mdf(mdf) fig, ax = dv.plot_mdf(mdf) plt.show()

Multiple properties:
fig, axes = DistrViz.multi(

{‘Grain area’: areas, ‘Aspect ratio’: ar, ‘Perimeter’: perim}, units_dict={‘Grain area’: ‘µm²’, ‘Aspect ratio’: ‘’, ‘Perimeter’: ‘µm’}, step_size=rdr.step_size,

) plt.show()

class upxo.viz.vizDistr.DistrViz(data, label='value', units='')[source]

Bases: object

Distribution visualiser for scalar grain properties and MDF data.

Parameters:
  • data (array-like) – 1-D array of values. NaN/Inf are stripped automatically.

  • label (str) – Property name — used in axis labels and titles.

  • units (str) – Unit string (e.g. ‘µm²’, ‘°’). Appended to x-label when non-empty.

classmethod from_mdf(mdf)[source]

Build from an mdf dict (output of compute_mdf_from_quats).

property stats

Dict of descriptive statistics computed from self.data.

print_stats()[source]

Print a compact statistics summary to stdout.

plot(vis='hist', bins=40, show_kde=True, show_stats=True, color='steelblue', figsize=(7, 4), log_scale=False, step_size=None, bw_method='scott', fill=True, ax=None)[source]

Unified plot dispatcher — routes to plot_hist, plot_kde, or plot_hist_kde based on vis.

Parameters:
  • vis (str) – 'hist', 'kde', or 'hist_kde'.

  • bins (int) – Histogram bin count (used by 'hist' and 'hist_kde').

  • show_kde (bool) – KDE overlay on histogram ('hist' only).

  • show_stats (bool) – Annotate mean / median lines.

  • color (str)

  • figsize (tuple)

  • log_scale (bool) – Log x-axis ('hist' only).

  • step_size (float or None) – Appended to x-label when provided.

  • bw_method (str or float) – KDE bandwidth selector ('kde' only).

  • fill (bool) – Fill KDE area ('kde' only).

  • ax (Axes or None)

Return type:

fig, ax

plot_hist(bins=40, show_kde=True, show_stats=True, color='steelblue', figsize=(7, 4), log_scale=False, step_size=None, ax=None)[source]

Histogram with optional KDE overlay and mean/median annotations.

Parameters:
  • bins (int)

  • show_kde (bool) – KDE curve scaled to match histogram counts.

  • show_stats (bool) – Draw vertical mean and median lines.

  • color (str)

  • figsize (tuple)

  • log_scale (bool) – Log x-axis.

  • step_size (float or None) – EBSD step size — appended to x-label when provided.

  • ax (Axes or None)

Return type:

fig, ax

plot_kde(bw_method='scott', fill=True, color='steelblue', show_stats=True, figsize=(7, 4), step_size=None, ax=None)[source]

Pure KDE plot (probability density).

Parameters:
  • bw_method (str or float) – Bandwidth selector passed to scipy.stats.gaussian_kde.

  • fill (bool) – Fill area under the KDE curve.

  • color – Standard plot options.

  • figsize – Standard plot options.

  • step_size – Standard plot options.

  • ax – Standard plot options.

Return type:

fig, ax

plot_hist_kde(bins=40, color='steelblue', show_stats=True, figsize=(7, 4), step_size=None, ax=None)[source]

Density-normalised histogram with KDE overlay.

Return type:

fig, ax

plot_mdf(mdf, show_csl=True, show_stats=True, angle_max=65.0, figsize=(8, 4), ax=None)[source]

Bar-chart MDF from a pre-computed mdf dict with optional CSL markers.

Lighter alternative to ebsdviz.plot_mdf — does not require the peaks dict. Use ebsdviz.plot_mdf when peak labels and KDE are needed.

Parameters:
  • mdf (dict) – Output of compute_mdf_from_quats. Required keys: ‘hist_bin_centers’, ‘hist_density’, ‘hist_bin_edges’, ‘n_pairs’, ‘mean_angle’, ‘std_angle’.

  • show_csl (bool) – Draw dashed vertical lines at common cubic CSL angles.

  • show_stats (bool) – Annotate mean ± std in the legend.

  • angle_max (float) – X-axis upper limit (degrees).

  • figsize (tuple)

  • ax (Axes or None)

Return type:

fig, ax

classmethod multi(data_dict, units_dict=None, step_size=None, bins=40, show_kde=True, show_stats=True, ncolumns=2, figsize_per=(5, 3.5), color='steelblue', log_scale=False)[source]

Plot distributions for multiple grain properties in a subplot grid.

Parameters:
  • data_dict (dict) – {label: array-like} of grain properties to plot.

  • units_dict (dict or None) – {label: units_str}. Missing keys default to no units.

  • step_size (float or None) – Passed to each subplot for x-label annotation.

  • bins (int)

  • show_kde (bool)

  • show_stats (bool)

  • ncolumns (int)

  • figsize_per (tuple) – (width, height) per panel in inches.

  • color (str)

  • log_scale (bool)

Return type:

fig, axes (axes is a flat ndarray)

upxo.viz.vizDistr.plot_grouped_distributions(data, prop_labels=None, group_colors=None, group_labels=None, bins=40, bw_method='scott', peak_prominence=0.01, figsize_per=(5, 4), dpi=110, suptitle='Property distributions by group', ncols=None, fontsize=9.0, show_hist=True, show_peaks=True, show_legend=True, show_line=True, kde_shaded=False, x_margin=0.03, do_tight_layout=True)[source]

Overlaid histogram + KDE + peak markers for multiple properties and groups.

Generic plotting function — no knowledge of grain structures or UPXO data formats. Data must be pre-extracted into plain arrays before calling.

Parameters:
  • data (dict) – {prop_name: {group_name: array-like}} — one entry per property, each containing one array per group. Arrays may be empty; empty/size-1 groups are silently skipped.

  • prop_labels (dict or None) – {prop_name: display_label} for axis / title text. Missing keys fall back to the prop_name itself.

  • group_colors (dict or None) – {group_name: colour_string}. Missing keys cycle through a default palette.

  • group_labels (dict or None) – {group_name: display_label} for legend entries. Missing keys fall back to the group_name itself.

  • bins (int) – Number of histogram bins (shared x-range across groups per property).

  • bw_method (str or float) – Bandwidth selector passed to scipy.stats.gaussian_kde.

  • peak_prominence (float) – Fraction of KDE maximum used as minimum prominence for find_peaks.

  • figsize_per (tuple) – (width, height) in inches per subplot panel.

  • dpi (int) – Figure resolution.

  • suptitle (str) – Figure-level title.

  • ncols (int or None) – Subplot grid columns. None places all panels in a single row.

  • fontsize (float) – Base font size; tick labels use fontsize-2, legend fontsize-2, peak annotations fontsize-3, suptitle fontsize+1.

  • show_hist (bool) – Draw histogram bars behind the KDE curves. Default True.

  • show_peaks (bool) – Draw vertical dashed lines and value annotations at KDE peaks. Default True.

  • show_legend (bool) – Draw a per-group legend on each subplot. Default True.

  • show_line (bool) – Draw the KDE density curve. Default True.

  • kde_shaded (bool) – Fill the area under each group’s KDE curve – ignored whenever show_hist is also True (see plot_property_distribution_on_axes for why). Default False.

  • x_margin (float) – Fractional padding added to both sides of the x-axis so that tick labels are never clipped at the axis boundary. Default 0.03.

  • do_tight_layout (bool) – Call plt.tight_layout() before returning. Set to False when the caller needs to adjust the figure (e.g. to add a colorbar) before finalising the layout. Default True.

Returns:

fig, axes

Return type:

Figure and 2-D axes array (shape (nrows, ncols_used)).

upxo.viz.vizDistr.plot_property_distribution_on_axes(ax, groups, xlabel, group_colors=None, group_labels=None, group_linestyles=None, group_linewidths=None, bins=40, bw_method='scott', peak_prominence=0.01, title_fontsize=12.0, axis_fontsize=10.0, tick_fontsize=9.0, legend_fontsize=9.0, show_hist=True, show_peaks=True, show_legend=True, show_line=True, show_rug=False, kde_shaded=False, legend_loc='upper right', rug_max_points=200, x_margin=0.03, title=None, ylabel=None, xlim=None, ylim=None)[source]

Overlaid histogram + KDE + peak markers for ONE property, drawn onto a caller-supplied ax – the single-axis unit plot_grouped_distributions builds a subplot grid of; extracted so a caller embedding a single persistent, in-place-redrawn Axes (e.g. a live GUI plot) can reuse the exact same drawing logic without going through plt.subplots() (which registers a new Figure with pyplot’s global state on every call) and without a multi-panel grid it doesn’t need.

Parameters:
  • ax (matplotlib Axes) – Drawn onto directly (not cleared first – callers that redraw in place should call ax.clear() themselves before calling this).

  • groups (dict) – {group_name: array-like} – one array per overlaid group. Arrays may be empty; empty/size-1 groups are silently skipped.

  • xlabel (str) – Property display label – used for both the x-axis label and the axes title (this function draws exactly one title; a caller wanting a figure-level title too, e.g. plot_grouped_distributions, adds its own fig.suptitle separately).

  • group_colors (dict or None) – Same shape/fallback convention as plot_grouped_distributions.

  • group_labels (dict or None) – Same shape/fallback convention as plot_grouped_distributions.

  • group_linestyles (dict or None) – {group_name: matplotlib linestyle} (e.g. ‘-’, ‘–’, ‘:’, ‘-.’). Missing keys fall back to ‘-‘.

  • group_linewidths (dict or None) – {group_name: float}. Missing keys fall back to 1.8.

  • title_fontsize (float) – Independent font sizes (previously a single fontsize with fixed offsets for tick/legend/peak-annotation text).

  • axis_fontsize (float) – Independent font sizes (previously a single fontsize with fixed offsets for tick/legend/peak-annotation text).

  • tick_fontsize (float) – Independent font sizes (previously a single fontsize with fixed offsets for tick/legend/peak-annotation text).

  • legend_fontsize (float) – Independent font sizes (previously a single fontsize with fixed offsets for tick/legend/peak-annotation text).

  • show_line (bool) – Draw the KDE density curve. Default True (matches this function’s historical always-on behaviour before this parameter existed).

  • show_rug (bool) – Draw a rug plot – one short tick per raw observation along the x-axis, coloured to match that group’s line – the standard way to show individual data points under a density curve. Ticks are drawn in a blended transform (x in data coordinates, y as a fixed fraction of the axes height) rather than data-scaled y coordinates, so they sit at a consistent, small height near the bottom of the plot regardless of the density curve’s own y-scale (a fixed data-unit offset here would either be invisible or dominate the plot, since “density” and the property’s own value range are unrelated scales). Default False.

  • legend_loc (str) – Passed straight to ax.legend(loc=…) – any matplotlib legend location string (e.g. ‘best’, ‘upper right’, ‘lower left’, ‘center’). Default ‘upper right’ (this function’s historical fixed placement).

  • rug_max_points (int) – A group with more than this many observations has its rug thinned to (approximately) this many ticks, evenly sampled across the SORTED values (not just the first N) so the rug still reads as a fair representation of the full distribution’s spread – large grain counts would otherwise render as a single solid smear rather than a legible rug. Default 200.

  • kde_shaded (bool) – Fill the area under each group’s KDE curve. Only takes effect when show_line is True AND show_hist is False – a shaded KDE fill drawn on top of histogram bars visually competes with them for the same area, so a caller asking for both gets the histogram (the more literal/less processed view) and the shading is silently dropped rather than erroring. Default False.

  • title (str or None) – Override this axes’ title / y-axis label. None (default) keeps the historical auto behaviour (title = xlabel, ylabel = “Density”) – an override is used verbatim, including an empty string (a caller wanting no title/ylabel at all can pass “”).

  • ylabel (str or None) – Override this axes’ title / y-axis label. None (default) keeps the historical auto behaviour (title = xlabel, ylabel = “Density”) – an override is used verbatim, including an empty string (a caller wanting no title/ylabel at all can pass “”).

  • xlim ((float, float) or None) – Explicit axis limits, applied AFTER this function’s own data-driven autoscaling (so they override it, not compete with it). None (default) keeps the auto behaviour.

  • ylim ((float, float) or None) – Explicit axis limits, applied AFTER this function’s own data-driven autoscaling (so they override it, not compete with it). None (default) keeps the auto behaviour.

Returns:

The property’s data range if anything was plotted (useful for a caller that wants to also know the x-limits actually used), or None if every group was empty/degenerate and nothing was drawn (ax is hidden via ax.set_visible(False) in that case).

Return type:

(vmin, vmax) or None

upxo.viz.vizDistr.plot_hist_with_slice_band(pooled_data, slice_arrays, label='value', units='', bins=40, show_stats=True, color='steelblue', band_color='steelblue', band_alpha=0.2, figsize=(7, 4), ax=None)[source]

Histogram + KDE of a pooled distribution (same presentation as DistrViz.plot_hist), with a shaded min/max envelope band overlaid around the KDE curve – at each x, the band spans the [min, max] across each individual slice’s own KDE (evaluated on the same grid, scaled to match the pooled KDE curve’s histogram-count scale), making slice-to-slice spread visible alongside the combined/pooled curve.

Typical use: pooled_data is the union of a property’s values from several 2D cross-sections of a 3D structure (e.g. TwinnedSimple3DBase.compute_2d_slice_properties’s pooled output), and slice_arrays is the same property’s per-slice breakdown (compute_2d_slice_properties_by_axis’s per-axis lists) – the main curve shows the combined distribution, the band shows how much an individual slice can vary from it.

Parameters:
  • pooled_data (array-like) – The combined (all slices) distribution – drives the histogram bars and the main KDE curve, exactly as DistrViz.plot_hist does with its own self.data.

  • slice_arrays (list of array-like) – One array per individual slice; each contributes one KDE curve to the shaded envelope. A slice with fewer than 2 finite, non-degenerate values is skipped (gaussian_kde needs a non-singular sample).

  • label (str) – Property name / unit string for axis labels and title.

  • units (str) – Property name / unit string for axis labels and title.

  • bins (int) – Histogram bin count.

  • show_stats (bool) – Draw vertical mean/median lines (pooled data).

  • color (str) – Histogram bar color.

  • band_color (str, float) – Fill color/opacity for the shaded envelope.

  • band_alpha (str, float) – Fill color/opacity for the shaded envelope.

  • figsize (tuple) – Only used when ax is None.

  • ax (Axes or None) – Plots into an existing axes if given; otherwise creates its own standalone figure.

Return type:

fig, ax

upxo.viz.vizDistr.plot_repr_rank(repr_rank_ng: dict, figsize=None, dpi: int = 100, fontsize_annot: float = 8.0, fontsize_tick: float = 9.0, fontsize_title: float = 9.0, fontsize_suptitle: float = 11.0) → None[source]

Five vertically stacked heatmaps showing the per-property rank of every MC time slice under each representativeness metric (ratio, Wasserstein, energy distance, KS statistic, Anderson–Darling statistic).

Colour encodes rank within each column independently: green = best (rank 1), red = worst (rank N). Cell text shows the raw numeric score. Rows are ordered best-to-worst by the aggregate score (inherited from the DataFrame sort order in repr_rank_ng).

Ranking rule per column: - ratio, property columns : rank by |value − 1| ascending

(closest to 1.0 = best)

  • ratio, aggregate column : rank by value ascending (lowest = best)

  • wasserstein / energy : rank by value ascending (lowest = best)

Parameters:
  • repr_rank_ng (dict) – {'ratio': df, 'wasserstein': df, 'energy': df} — as stored in repgen2d.repr_rank_ng after calling find_repr_mcgs_props.

  • figsize (tuple or None) – Override default figure size. Default auto-computes from data shape.

  • dpi (int) – Figure resolution.

  • fontsize_annot (float) – Font size for the numeric value printed in each cell.

  • fontsize_tick (float) – Font size for axis tick labels (slice keys on y-axis, column names on x-axis).

  • fontsize_title (float) – Font size for each panel title.

  • fontsize_suptitle (float) – Font size for the overall figure title.

upxo.viz.vizDistr.plot_normalized_prop_distributions(ebsd_data: dict, mc_data: dict, props: list, scores: dict | None = None, prop_labels: dict | None = None, bins: int = 40, bw_method='scott', figsize_per: tuple = (5, 4), dpi: int = 100, ncols: int | None = None, fontsize: float = 9.0, show_hist: bool = True, show_peaks: bool = True, legend_loc: str = 'upper right', legend_ncol: int = 1, legend_fontsize: float | None = None) → None[source]

Overlaid normalised property distributions for EBSD (merged) and MC slices.

Each distribution is normalised by its own mean before plotting, matching the normalisation used in find_repr_mcgs_props. All curves are therefore centred near 1.0 on the x-axis and are directly shape-comparable.

Wasserstein and energy distances are annotated in each subplot legend when scores is provided.

Parameters:
  • ebsd_data (dict) – {prop: array} of EBSD-merged property values, each already divided by its own mean.

  • mc_data (dict) – {slice_key: {prop: array}} of MC property values, each already divided by its own mean.

  • props (list of str) – Ordered list of property names to plot.

  • scores (dict or None) – {slice_key: {prop: {'wasserstein': v, 'energy': v}}} extracted from repr_rank_ng. When supplied, each MC curve’s legend entry is annotated with W=...  E=... for the per-property distance.

  • prop_labels (dict or None) – {prop: display_label}. Defaults to f'{prop}  (mean normalized)'.

  • bins – Forwarded to plot_grouped_distributions().

  • bw_method – Forwarded to plot_grouped_distributions().

  • figsize_per – Forwarded to plot_grouped_distributions().

  • dpi – Forwarded to plot_grouped_distributions().

  • ncols – Forwarded to plot_grouped_distributions().

  • fontsize – Forwarded to plot_grouped_distributions().

  • show_hist – Forwarded to plot_grouped_distributions().

  • show_peaks – Forwarded to plot_grouped_distributions().

  • legend_loc (str) – Legend location string passed to ax.legend(loc=...). Examples: 'upper right', 'upper left', 'lower right', 'center left', 'best'. Default 'upper right'.

  • legend_ncol (int) – Number of columns in the legend. Values > 1 split entries side-by-side, reducing legend height and — when entries are uniform in width — the overall legend footprint. Default 1 (single column).

  • legend_fontsize (float or None) – Font size for legend text. Reducing this is the most direct way to shrink the legend box since box width is driven by label text length. Defaults to fontsize - 2 when None.

upxo.viz.vizDistr.plot_qq_comparison(ebsd_data: dict, mc_data: dict, props: list, prop_labels: dict | None = None, figsize_per: tuple = (4, 4), dpi: int = 100, ncols: int | None = None, fontsize: float = 9.0) → None[source]

Quantile–Quantile (Q-Q) comparison of EBSD vs MC grain property distributions.

A Q-Q plot maps the quantiles of one distribution against the quantiles of another at the same probability levels (0 % to 100 %). Both distributions are normalised by their own mean before comparison, so the x- and y-axes share the same dimensionless scale centred near 1.0.

Interpretation

  • Points on the diagonal (y = x) — the two distributions have identical shape at that quantile. Perfect agreement.

  • Points above the diagonal — the MC distribution has larger values than EBSD at that quantile (heavier upper tail or higher spread in MC).

  • Points below the diagonal — the MC distribution has smaller values than EBSD at that quantile.

  • Deviations concentrated in the lower-left — fine/small grains differ.

  • Deviations concentrated in the upper-right — large/coarse grains differ.

One subplot is drawn per property; each MC slice is a separate line. The dashed black diagonal marks perfect distributional agreement.

param ebsd_data:

{prop: array} of EBSD-merged values, each normalised by own mean.

type ebsd_data:

dict

param mc_data:

{slice_key: {prop: array}} of MC values, each normalised by own mean.

type mc_data:

dict

param props:

Properties to plot.

type props:

list of str

param prop_labels:

{prop: display_label}. Defaults to f'{prop}  (mean normalized)'.

type prop_labels:

dict or None

param figsize_per:

(width, height) per subplot in inches.

type figsize_per:

tuple

param dpi:

type dpi:

int

param ncols:

Subplot grid columns. None places all panels in a single row.

type ncols:

int or None

param fontsize:

type fontsize:

float

upxo.viz.vizDistr.plot_ebsd_tvf(tvf_result: dict, figsize: tuple = (7, 4), dpi: int = 100, fontsize: float = 9.0, title: str = 'EBSD grain-role area fractions') → None[source]

Horizontal bar chart of EBSD twin area fraction broken down by grain role.

Bars are drawn for each of the four grain-role categories:

  • Pure parents — matrix grains; never a twin of any grain.

  • Primary twins — first-generation twins whose parent is a pure parent.

  • Secondary twins — twins whose parent is itself an intermediate (twin-of-a-twin, 2nd generation).

  • Intermediate twins — grains that are simultaneously a twin of one grain and a parent of another (twin chains).

The overall twin area fraction (primary + secondary + intermediate) is annotated on the figure.

Parameters:
  • tvf_result (dict) – Output of repgen2d.compute_ebsd_tvf. Must contain keys 'pure_parent_frac', 'primary_twin_frac', 'secondary_twin_frac', 'intermediate_frac', 'overall_twin_frac'.

  • figsize (tuple) – Figure size (width, height) in inches.

  • dpi (int) – Figure resolution.

  • fontsize (float) – Base font size for labels and tick marks.

  • title (str) – Figure title.