A NumPy universal function, or ufunc, applies an operation element by element across arrays while supporting broadcasting. This model encourages vectorized array processing, but does not make arbitrary Python code fast: np.vectorize is essentially a Python-level loop and can be much slower than native NumPy operations. section 4.2.4
Overflow-safe image transformation
Define double_wo_overflow(v) as min(2 * v, 255), wrap it with np.vectorize(..., otypes=[np.uint8]), and apply it to an image array. The result preserves the uint8 range, but timing shows the vectorized wrapper operates in milliseconds versus microseconds for native multiplication. section 4.2.4
Generalized elementwise processing
A generalized ufunc can consume one RGB pixel with signature (n)->() and emit one grayscale value. The same grayscale result can instead use np.mean(image_arr, axis=2).astype(np.uint8), avoiding the Python-level vectorized loop. section 4.2.4
Cython can implement a compiled ufunc for pixel processing. A typed function receives pointers, writes through an output variable, and can release the GIL with nogil; registration supplies data types, loop functions, and the generalized signature. Broadcasting and pixel-by-pixel execution remain available without explicit whole-image loops. section 5.5
Ufuncs are not universal computing solutions: algorithms that inspect other array positions or maintain broader state may require a different processing design. section 5.5.1
