upxo.connops.neighbour_ops module

Module: neighbour_ops

Neighbour (connectivity) operations for 2D grain structures.

Identifies neighbouring grain IDs and grain-boundary pixel segments for individual grains or across an entire grain structure. Provides first- through nth-order neighbourhood queries with optional Numba acceleration for large datasets.

Usage

import upxo.connops.neighbour_ops as neighOps
upxo.connops.neighbour_ops.NMB_get_neighbor_mask(neigh_mask, gid)

Mark the boundary pixels of a single grain in a 2D label sub-array.

JIT-compiled with Numba (parallel outer loop via prange). For every pixel that belongs to gid, the four 4-connected neighbours that do not belong to gid are marked -1. All remaining pixels are set to 0.

Parameters:
  • neigh_mask (numpy.ndarray of int, shape (R, C)) – Sub-array of the full labelled image covering the extended bounding box of the grain. Modified in-place and returned.

  • gid (int) – Label value of the grain whose boundary-adjacent pixels are to be identified.

Returns:

neigh_mask – Same array, now containing -1 at pixels that are direct 4-connected neighbours of gid but belong to a different grain, and 0 everywhere else.

Return type:

numpy.ndarray of int, shape (R, C)

Notes

The outer row loop is parallelised with prange; the inner column loop is sequential. Bounds checks are explicit — no zero-padding of the input array is required.

upxo.connops.neighbour_ops.find_neigh_fid(gdict, lfi, fid, nfeatures, include_central_grain=False, update_grain_object=True, user_defined_bbox_ex_bounds=False, bbox_ex_bounds_fid=None, use_numba=False, _char_fx_version_=2, get_gbsegs=True, save_gbsegs=False, dtype_gbseg=numpy.int32, throw=False, throw_gbsegs=False)[source]

Find the neighbour IDs of a single grain and optionally its boundary segments.

Extracts the extended bounding-box sub-array for fid, constructs a neighbour mask (via Numba or pure Python depending on nfeatures), and returns the unique IDs of all directly touching grains. Grain-boundary segment maps can be generated and stored in the grain object’s sparse format.

Parameters:
  • gdict (dict) – Grain-object dictionary keyed by grain ID. Each value is either a grain object directly (_char_fx_version_=2) or a dict with a 'grain' key (_char_fx_version_=1).

  • lfi (numpy.ndarray of int, shape (R, C)) – Full labelled feature image for the grain structure.

  • fid (int) – Grain ID for which neighbours are to be found.

  • nfeatures (int) – Total number of grains in the structure. Governs the Numba/pure-Python branch threshold (Numba is used when nfeatures > 2000 or use_numba is True).

  • include_central_grain (bool, default False) – Include fid itself in the returned neighbour array.

  • update_grain_object (bool, default True) – Write the neighbour ID array into the grain object as grain.neigh.

  • user_defined_bbox_ex_bounds (bool, default False) – Use bbox_ex_bounds_fid instead of the bounding-box stored in the grain object.

  • bbox_ex_bounds_fid (tuple of int or None, default None) – (rmin, rmax, cmin, cmax) bounding-box extents to use when user_defined_bbox_ex_bounds is True.

  • use_numba (bool, default False) – Force use of NMB_get_neighbor_mask() regardless of nfeatures.

  • _char_fx_version_ ({1, 2}, default 2) – Selects how grain objects are accessed from gdict. Version 2 accesses grain attributes directly; version 1 assumes a nested dict with a 'grain' key.

  • get_gbsegs (bool, default True) – Compute the grain-boundary segment map (a 2D array where each non-zero pixel contains the neighbour grain’s ID at that boundary location).

  • save_gbsegs (bool, default False) – Serialise the boundary segment map into the grain object in sparse format (shape, NZI, NZV, dtype keys).

  • dtype_gbseg (numpy dtype, default numpy.int32) – Data type recorded in the sparse dtype field when saving segments.

  • throw (bool, default False) – Return neighbour_ids (or a tuple) instead of None.

  • throw_gbsegs (bool, default False) – When both get_gbsegs and throw_gbsegs are True, return (neighbour_ids, gbsegs) instead of neighbour_ids alone.

Returns:

  • None – When throw is False.

  • neighbour_ids (numpy.ndarray of int) – Unique IDs of all grains directly touching fid. Returned when throw is True and throw_gbsegs is False.

  • (neighbour_ids, gbsegs) (tuple) – Tuple of the neighbour ID array and the 2D boundary segment map. Returned when both throw and throw_gbsegs are True.

Notes

Grain-boundary segments are always stored in the grain object in sparse format (non-zero indices and values). To reconstruct the dense array use the lfi_gbsegs property of the grain object, or rebuild manually from the shape, NZI, and NZV fields.

Examples

Typical usage through the grain-structure object (which calls this function internally):

from upxo.ggrowth.mcgs import mcgs
pxtal = mcgs(input_dashboard='input_dashboard.xls')
pxtal.simulate()
pxtal.detect_grains()
tslice = 10
pxtal.gs[tslice].char_morph_2d(char_gb=True)
pxtal.gs[tslice].find_neigh(include_central_grain=False)
print(pxtal.gs[tslice].neigh_gid[10])

For direct use at module level:

import upxo.connops.neighbour_ops as neighOps
neigh_ids = neighOps.find_neigh_fid(
    gdict, lfi, fid=5, nfeatures=200,
    throw=True, throw_gbsegs=False)
upxo.connops.neighbour_ops.find_neigh(lfi, fids, gdict, nfeatures, include_central_grain=False, char_fx_version=2, print_msg=True, user_defined_bbox_ex_bounds=False, bbox_ex_bounds=None, update_grain_object=True, use_numba=False)[source]

Find the neighbour IDs for every grain in the structure.

Iterates over all grain IDs in fids, calls find_neigh_fid() for each, and collects the results into a single dictionary.

Parameters:
  • lfi (numpy.ndarray of int, shape (R, C)) – Full labelled feature image for the grain structure.

  • fids (iterable of int) – Grain IDs to process. Typically range(1, nfeatures + 1).

  • gdict (dict) – Grain-object dictionary keyed by grain ID (see find_neigh_fid()).

  • nfeatures (int) – Total number of grains; governs the Numba/pure-Python branch in find_neigh_fid().

  • include_central_grain (bool, default False) – Include each grain’s own ID in its neighbour list.

  • char_fx_version ({1, 2}, default 2) – Grain-object access version forwarded to find_neigh_fid().

  • print_msg (bool, default True) – Print a progress message before the loop begins.

  • user_defined_bbox_ex_bounds (bool, default False) – Use bounding-box extents from bbox_ex_bounds instead of those stored in the grain objects.

  • bbox_ex_bounds (dict or None, default None) – Mapping of grain ID → (rmin, rmax, cmin, cmax) when user_defined_bbox_ex_bounds is True.

  • update_grain_object (bool, default True) – Write each grain’s neighbour array into its grain object.

  • use_numba (bool, default False) – Force Numba acceleration regardless of nfeatures.

Returns:

neigh_gid – Mapping of grain ID → list of neighbour grain IDs.

Return type:

dict[int, list of int]

Examples

from upxo.ggrowth.mcgs import mcgs
pxtal = mcgs(input_dashboard='input_dashboard.xls')
pxtal.simulate()
pxtal.detect_grains()
tslice = 10
pxtal.gs[tslice].char_morph_2d(char_gb=True)

# With central grain included
pxtal.gs[tslice].find_neigh(include_central_grain=True)
print(pxtal.gs[tslice].neigh_gid[10])

# Without central grain
pxtal.gs[tslice].find_neigh(include_central_grain=False)
print(pxtal.gs[tslice].neigh_gid[10])
upxo.connops.neighbour_ops.get_upto_nth_order_neighbors(neigh_gid, grain_id, neigh_order, include_parent=True, output_type='list')[source]

Return all grains reachable within neigh_order hops from grain_id.

Starting from the 1st-order neighbours of grain_id, the function iteratively expands the set by one hop per order until neigh_order hops are exhausted.

Parameters:
  • neigh_gid (dict[int, list of int]) – Pre-computed neighbour map — typically gs.neigh_gid populated by find_neigh().

  • grain_id (int) – ID of the central grain.

  • neigh_order (int) – Maximum hop count. 1 returns direct neighbours; 2 returns neighbours-of-neighbours, and so on. 0 returns [grain_id].

  • include_parent (bool, default True) – Include grain_id in the returned set.

  • output_type ({'list', 'nparray', 'set'}, default 'list') – Return type. 'nparray' also accepts 'np', 'np.array', 'numpy'.

Returns:

All grain IDs within neigh_order hops of grain_id.

Return type:

list, numpy.ndarray, or set

Examples

import upxo.connops.neighbour_ops as neighOps
# neigh_gid populated by find_neigh or find_neigh_v2
result = neighOps.get_upto_nth_order_neighbors(
    gs.neigh_gid, grain_id=6, neigh_order=3,
    include_parent=True, output_type='list')

See also

get_nth_order_neighbors

Returns only the grains at exactly order n.

upxo.connops.neighbour_ops.get_nth_order_neighbors(neigh_gid, grain_id, neigh_order, include_parent=True)[source]

Return the grains reachable in exactly neigh_order hops.

Computes the set difference between the up-to-n and up-to-(n-1) neighbourhood sets to isolate grains that first become reachable at exactly the requested order.

Parameters:
  • neigh_gid (dict[int, list of int]) – Pre-computed neighbour map — typically gs.neigh_gid.

  • grain_id (int) – ID of the central grain.

  • neigh_order (int) – Exact hop count. Grains reachable in fewer hops are excluded.

  • include_parent (bool, default True) – Include grain_id in the candidate set before differencing.

Returns:

Grain IDs reachable in exactly neigh_order hops from grain_id.

Return type:

list of int

Examples

import upxo.connops.neighbour_ops as neighOps
result = neighOps.get_nth_order_neighbors(
    gs.neigh_gid, grain_id=10, neigh_order=2, include_parent=True)
upxo.connops.neighbour_ops.get_upto_nth_order_neighbors_all_grains(neigh_gid, gids, neigh_order, include_parent=True, output_type='list')[source]

Return up-to-nth-order neighbours for every grain.

Calls get_upto_nth_order_neighbors() for each grain ID in gids and collects the results into a dictionary.

Parameters:
  • neigh_gid (dict[int, list of int]) – Pre-computed neighbour map — typically gs.neigh_gid.

  • gids (array-like of int) – Grain IDs to process — typically gs.gid.

  • neigh_order (int) – Maximum hop count.

  • include_parent (bool, default True) – Include each grain’s own ID in its neighbour list.

  • output_type ({'list', 'nparray', 'set'}, default 'list') – Return type for each grain’s neighbour collection.

Returns:

Mapping of grain ID → neighbours up to order neigh_order.

Return type:

dict[int, list or numpy.ndarray or set]

Examples

import upxo.connops.neighbour_ops as neighOps
result = neighOps.get_upto_nth_order_neighbors_all_grains(
    gs.neigh_gid, gs.gid, neigh_order=2,
    include_parent=True, output_type='list')
print(result[5])  # neighbours of grain 5 up to order 2
upxo.connops.neighbour_ops.get_nth_order_neighbors_all_grains(neigh_gid, gids, neigh_order, include_parent=True)[source]

Return the exactly-nth-order neighbours for every grain.

Calls get_nth_order_neighbors() for each grain ID in gids and collects results into a dictionary. Only grains first reachable at exactly neigh_order hops are included.

Parameters:
  • neigh_gid (dict[int, list of int]) – Pre-computed neighbour map — typically gs.neigh_gid.

  • gids (array-like of int) – Grain IDs to process — typically gs.gid.

  • neigh_order (int) – Exact hop count.

  • include_parent (bool, default True) – Include each grain’s own ID in the candidate set before differencing.

Returns:

Mapping of grain ID → list of grains reachable in exactly neigh_order hops.

Return type:

dict[int, list of int]

Examples

import upxo.connops.neighbour_ops as neighOps
result = neighOps.get_nth_order_neighbors_all_grains(
    gs.neigh_gid, gs.gid, neigh_order=2, include_parent=True)
print(result[10])  # grains at exactly order 2 from grain 10
upxo.connops.neighbour_ops.get_upto_nth_order_neighbors_all_grains_prob(neigh_gid, gids, neigh_order, include_parent=False, print_msg=False, _int_approx_=0.05)[source]

Return up-to-nth-order neighbours for all grains with probabilistic fractional orders.

Extends get_upto_nth_order_neighbors_all_grains() to accept non-integer neigh_order values. A fractional order such as 1.5 returns all grains up to order 1 plus a random subset of order-2 grains, where the fraction included equals the decimal part of neigh_order.

Parameters:
  • neigh_order (int or float) –

    Neighbourhood order.

    • Integer (or float within _int_approx_ of an integer): delegates directly to get_upto_nth_order_neighbors_all_grains().

    • Non-integer float (e.g. 1.5): returns all up-to-floor neighbours plus ceil(decimal_part × |order-n neighbours|) randomly chosen grains from the next order shell.

  • recalculate (bool, default False) – Force recomputation of the base neighbour map.

  • include_parent (bool, default False) – Include each grain’s own ID in its neighbour set.

  • print_msg (bool, default False) – Print a diagnostic message indicating which branch was taken.

  • _int_approx_ (float, default 0.05) – Maximum distance from the nearest integer for neigh_order to be treated as an integer.

Returns:

When neigh_order is (effectively) an integer, a mapping of grain ID → neighbours up to that integer order.

When neigh_order is a true fraction, a mapping of grain ID → blended neighbour list (floor-order neighbours + probabilistic sample of the next order shell).

Return type:

dict[int, list of int]

Raises:

ValueError – If neigh_order is neither an int nor a float.

Examples

from upxo.ggrowth.mcgs import mcgs
pxtal = mcgs(input_dashboard='input_dashboard.xls')
pxtal.simulate()
pxtal.detect_grains()
tslice = 10
fn = pxtal.gs[tslice].get_upto_nth_order_neighbors_all_grains_prob

neigh_int  = fn(1,    recalculate=False, include_parent=True)
neigh_near = fn(1.06, recalculate=False, include_parent=True)
neigh_half = fn(1.5,  recalculate=False, include_parent=True)

print(neigh_int[22])     # integer-order result for grain 22
print(neigh_near[22])    # near-integer result (treated as order 1)
print(neigh_half[22])    # probabilistic blend for grain 22