Source code for ewoksfluo.units

from typing import Dict
from typing import List
from typing import Literal
from typing import Optional
from typing import Sequence
from typing import Tuple
from typing import Union

import numpy
import pint


[docs] def unit_registry() -> pint.UnitRegistry: return pint.get_application_registry()
[docs] def units_as_str(units: pint.Unit) -> str: return f"{units:~}"
[docs] def convert_units_to_group_reference( values: Sequence[numpy.ndarray], units: Sequence[Optional[str]], reference_units: Optional[Dict[str, str]] = None, fallback: Optional[Literal["first", "largest", "smallest"]] = "smallest", ) -> Tuple[List[numpy.ndarray], List[str]]: """ Convert values so that units sharing the same dimensionality are expressed in a common (reference) unit. Priority: 1. Use explicit reference_units mapping per dimensionality 2. Otherwise fallback to strategy ("first", "largest", "smallest") Example: units = ["um", "mm", "deg", "rad"] reference_units = {"[angle]": "deg"} fallback = "largest" -> ["mm", "mm", "deg", "deg"] :param values: sequence of numpy arrays (one per axis) :param units: sequence of unit strings :param reference_units: mapping dimensionality -> unit :param fallback: strategy for unspecified dimensionalities :returns: (converted_values, normalized_units) """ ureg = unit_registry() normalized_units = normalize_units(units) groups = _group_units_by_dimensionality(normalized_units) new_values = list(values) new_units = list(normalized_units) if reference_units is None: reference_units = {} for dim_key, indices in groups.items(): if dim_key in reference_units: ref_unit = reference_units[dim_key] ref_parsed = ureg.parse_units(ref_unit) elif fallback is not None: ref_idx = _select_reference_index(indices, normalized_units, ureg, fallback) ref_unit = normalized_units[ref_idx] ref_parsed = ureg.parse_units(ref_unit) else: continue for i in indices: parsed = ureg.parse_units(normalized_units[i]) new_values[i] = (values[i] * parsed).to(ref_parsed).magnitude new_units[i] = ref_unit return new_values, new_units
[docs] def convert_values_to_units( values: Optional[Sequence[Union[float, Tuple[float, str]]]], target_units: Sequence[Optional[str]], ) -> Optional[List[float]]: """ Convert values to match target units per axis. :param values: sequence of floats or (value, unit) tuples :param target_units: units per axis :returns: values in consistent units """ if values is None: return None ureg = unit_registry() result: List[float] = [] normalized_units = normalize_units(target_units) for value, target_unit in zip(values, normalized_units): if isinstance(value, tuple): v, u = value parsed = ureg.parse_units(u) target = ureg.parse_units(target_unit) v = (v * parsed).to(target).magnitude result.append(float(v)) else: result.append(float(value)) return result
[docs] def normalize_units(units: Sequence[Optional[str]]) -> List[str]: """ Replace None with 'dimensionless' """ return [u if u is not None else "dimensionless" for u in units]
def _group_units_by_dimensionality( units: Sequence[str], ) -> Dict[str, List[int]]: """ Group indices of units by dimensionality. """ ureg = unit_registry() groups: Dict[str, List[int]] = {} for i, u in enumerate(units): parsed = ureg.parse_units(u) key = str(parsed.dimensionality) groups.setdefault(key, []).append(i) return groups def _select_reference_index( indices: List[int], units: Sequence[str], ureg: pint.UnitRegistry, strategy: Optional[Literal["first", "largest", "smallest"]], ) -> int: if strategy is None: raise ValueError( "No fallback strategy provided and no reference unit specified " "for this dimensionality" ) if strategy == "first": return indices[0] if strategy == "smallest": return min(indices, key=lambda i: _get_scale(units[i], ureg)) if strategy == "largest": return max(indices, key=lambda i: _get_scale(units[i], ureg)) raise ValueError("fallback must be 'first', 'largest', 'smallest', or None") def _get_scale(unit: str, ureg: pint.UnitRegistry) -> float: """ Return scale of unit in base units (used for comparison). """ return (1 * ureg.parse_units(unit)).to_base_units().magnitude