Source code for upxo.imageOps.labelops

"""Label and connected-component operations for UPXO image data."""

import numpy as np
from scipy.ndimage import generate_binary_structure
from scipy.ndimage import label as scipy_label


[docs] def reindex_labels(label_image, background=0, start=1): """Reindex non-background labels to consecutive integers.""" label_image = np.asarray(label_image) unique_labels = np.unique(label_image) unique_labels = unique_labels[unique_labels != background] label_map = {old: new for new, old in enumerate(unique_labels, start=start)} vectorized_map = np.vectorize(lambda x: label_map.get(x, background)) return vectorized_map(label_image)
[docs] def reindex_arrays_by_image_pair(old_image, new_image, old_arrays): """Reindex ID arrays using the old-image to new-image mapping.""" old_image = np.asarray(old_image) new_image = np.asarray(new_image) unique_pairs = np.unique( np.vstack((old_image.ravel(), new_image.ravel())).T, axis=0 ) old_to_new = {old_id: new_id for old_id, new_id in unique_pairs} return [np.array([old_to_new.get(old_id) for old_id in old_array], dtype=np.int32) for old_array in old_arrays]
[docs] def binary_structure(ndim=3, connectivity=1): """Return a SciPy binary structure for connected-component labelling.""" if connectivity not in tuple(range(1, ndim+1)): raise ValueError(f"connectivity must be in [1, {ndim}].") return generate_binary_structure(ndim, connectivity)
[docs] def label_components(binary_image, connectivity=1, labeler=None): """Label one binary image using SciPy-compatible labelling.""" structure = binary_structure(np.ndim(binary_image), connectivity) labeler = scipy_label if labeler is None else labeler return labeler(np.asarray(binary_image).astype(np.uint8), structure=structure)
[docs] def label_multistate_components(state_image, states=None, connectivity=1, labeler=None): """Label each state independently and merge labels into one image.""" state_image = np.asarray(state_image) states = np.unique(state_image) if states is None else np.asarray(states) lgi = None s_gid = {} s_n = [] gid_s = [] for i, state in enumerate(states): labels, _ = label_components(state_image == state, connectivity=connectivity, labeler=labeler) labels = labels.astype(np.int32, copy=False) if i == 0: lgi = labels else: labels[labels > 0] += int(lgi.max()) lgi = lgi + labels gids = tuple(np.delete(np.unique(labels), 0)) s_gid[int(state)] = gids s_n.append(len(gids)) gid_s.extend([int(state) for _ in gids]) return lgi.astype(np.int32, copy=False), s_gid, s_n, gid_s
[docs] def coerce_lgi_uint8(lgi): """Return a uint8 2D labelled image or None when invalid.""" if isinstance(lgi, np.ndarray) and np.size(lgi) > 0 and np.ndim(lgi) == 2: return lgi if lgi.dtype.name == 'uint8' else lgi.astype(np.uint8) return None