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: Literal["first", "largest", "smallest"] = "first",
) -> Tuple[List[numpy.ndarray], List[str]]:
"""
Convert values so that units sharing the same dimensionality
are expressed in a common (reference) unit.
The reference unit is the first occurrence in each group.
Example:
units = ["um", "mm", "deg", "rad"]
-> ["um", "um", "deg", "deg"]
:param values: sequence of numpy arrays (one per axis)
:param units: sequence of unit strings
:returns: (converted_values, normalized_units)
"""
ureg = unit_registry()
normalized_units = normalize_units(units)
groups = _group_units_by_dimensionality(
normalized_units, reference_units=reference_units
)
new_values = list(values)
new_units = list(normalized_units)
for indices in groups.values():
ref_idx = indices[0]
ref_unit = normalized_units[ref_idx]
ref_parsed = ureg.parse_units(ref_unit)
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 = []
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]:
return [u if u is not None else "dimensionless" for u in units]
def _group_units_by_dimensionality(
units: Sequence[str],
reference_units: Literal["first", "largest", "smallest"] = "first",
) -> Dict[str, List[int]]:
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)
if reference_units != "first":
for key, indices in groups.items():
if reference_units == "smallest":
ref_idx = min(indices, key=lambda i: _get_scale(units[i], ureg))
elif reference_units == "largest":
ref_idx = max(indices, key=lambda i: _get_scale(units[i], ureg))
else:
raise ValueError(
"reference_units must be 'first', 'largest' or 'smallest'"
)
# move reference index to first position
groups[key] = [ref_idx] + [i for i in indices if i != ref_idx]
return groups
def _get_scale(unit: str, ureg) -> float:
return (1 * ureg.parse_units(unit)).to_base_units().magnitude