Source code for arrlp.modules.compress_LP.compress

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Date          : 2025-11-30
# Author        : Lancelot PINCET
# GitHub        : https://github.com/LancelotPincet
# Library       : arrLP
# Module        : compress

"""
Compresses an array between values by normalizing, with possibility to saturate extrema.
"""



# %% Libraries
import numpy as np
from numba import njit
from arrlp import get_xp



# %% Function
def _nanpercentile(array, percentile, xp):
    """Compute nan-aware percentile with a fallback backend path."""
    if hasattr(xp, "nanpercentile"):
        return xp.nanpercentile(array, percentile)
    finite_values = array[xp.isfinite(array)]
    if finite_values.size == 0:
        return xp.nan
    return xp.percentile(finite_values, percentile)


[docs] def compress(array, /, max=1, min=0, *, dtype=None, out=None, stacks=False, channels=False, white=None, black=None, white_percent=None, black_percent=None, saturate=None) : ''' Compresses an array between values by normalizing, with possibility to saturate extrema. Parameters ---------- array : np.ndarray Array to normalize. max : int or float or None white value in output. None for no changing of white min : int or float or None black value in output. None for no changing of black dtype : np.dtype or str or None dtype of output, None for same as input out : array Array where to save, if None will create new array. stacks : bool True to apply on stacks. channels : bool True to apply on channels. white : int or float or None white value in input. None for maximum black : int or float or None black value in input. None for minimum white_percent : int or None white percentage distribution in input if white is None. black_percent : int or None black percentage distribution in input if black is None. saturate : Any or bool or None If True, will saturate values above white and black. If Any, will replace by this value. If None no saturation. Returns ------- array : np.ndarray Normalized and saturated copy of array. Examples -------- >>> from arrlp import compress >>> array = np.arange(100, dtype=np.float32) ... >>> compress(array, 10, 5) # compresses array between 10 and 5 >>> compress(array, white=50, black=40) # compresses array linearly so that 50 value is at 1 and 40 is at 0 >>> compress(array, white=50, black=40, saturate=np.nan) # compresses array linearly so that 50 value is at 1 and 40 is at 0, replace outside values by np.nan >>> compress(array, white_percent=10, black=1) # compresses array linearly so that 10% of array will be white, and 1% black (without saturation) >>> compress(array, white_percent=10, black=1, saturate=True) # compresses array linearly so that 10% of array will be white, and 1% black (with saturation) ''' # init xp = get_xp(array) array = xp.asarray(array) if out is None : out = xp.empty_like(array, dtype=dtype) if dtype is not None and out.dtype != dtype : raise TypeError('Out dtype and asked dtype do not correspond') out[:] = array # Stacks and channels if stacks : for i in range(len(out)) : compress(out[i], out=out[i], channels=channels, max=max, min=min, white=white, black=black, white_percent=white_percent, black_percent=black_percent, saturate=saturate) return out elif channels : for i in range(out.shape[-1]) : compress(out[..., i], out=out[..., i], max=max, min=min, white=white, black=black, white_percent=white_percent, black_percent=black_percent, saturate=saturate) return out # Get white/black if white is None : white = xp.nanmax(array) if white_percent is None else _nanpercentile(array, 100-white_percent, xp) if black is None : black = xp.nanmin(array) if black_percent is None else _nanpercentile(array, black_percent, xp) if white <= black : fill_value = min if min is not None else black out[...] = fill_value return out # Normalization if max is not None and min is not None and min >= max : raise ValueError('min >= max is not possible while compressing') if max is not None : normalization(out, value=max, norm=white, fix=black, xp=xp) white = max if min is not None : normalization(out, value=min, norm=black, fix=white, xp=xp) black = min # Saturation if saturate is not None : if saturate is True : sat_min, sat_max = black, white else : sat_min, sat_max = saturate, saturate out[out>white] = sat_max out[out<black] = sat_min return out
def normalization(array, /, value:float=None, norm:float=None, fix:float=None, xp=np): '''Basic normalization process of array copy while keeping a fixed point''' if fix is None : fix = 0 if norm is None : norm = max(xp.nanmax(array),-xp.nanmin(array)) if value is None : value = 1*xp.sign(norm) if xp is np: njit_normalize(array.ravel(), value, norm, fix) else: array[...] = (array - fix) / (norm - fix) * (value - fix) + fix @njit(nogil=True, cache=True, fastmath=True) def njit_normalize(array, value, norm, fix) : for i in range(len(array)) : array[i] = (array[i]-fix)/(norm-fix)*(value-fix) + fix # %% Test function run if __name__ == "__main__": from corelp import test test(__file__)