Source code for arcade_collection.output.convert_model_units

from __future__ import annotations

import re
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    import pandas as pd


[docs]def convert_model_units( data: pd.DataFrame, ds: float | None, dt: float | None, regions: list[str] | str | None = None, ) -> None: """ Convert data from simulation units to true units. Simulations use spatial unit of voxels and temporal unit of ticks. Spatial resolution (microns/voxel) and temporal resolution (hours/tick) are used to convert data to true units. If spatial or temporal resolution is not given, they will be estimated from the ``KEY`` column of the data. The following columns are added to the data: ============= =================== ============================= Target column Source column(s) Conversion ============= =================== ============================= ``time`` ``TICK`` ``dt * TICK`` ``volume`` ``NUM_VOXELS`` ``ds * ds * ds * NUM_VOXELS`` ``height`` ``MAX_Z`` ``MIN_Z`` ``ds * (MAX_Z - MIN_Z + 1)`` ``cx`` ``CENTER_X`` ``ds * CENTER_X`` ``cy`` ``CENTER_Y`` ``ds * CENTER_Y`` ``cz`` ``CENTER_Z`` ``ds * CENTER_Z`` ============= =================== ============================= For each region (other than ``DEFAULT``), the following columns are added to the data: ================= ================================= ========================================== Target column Source column(s) Conversion ================= ================================= ========================================== ``volume.REGION`` ``NUM_VOXELS.REGION`` ``ds * ds * ds * NUM_VOXELS.REGION`` ``height.REGION`` ``MAX_Z.REGION`` ``MIN_Z.REGION`` ``ds * (MAX_Z.REGION - MIN_Z.REGION + 1)`` ================= ================================= ========================================== The following property columns are rescaled: ===================== ===================== ========================== Target column Source column(s) Conversion ===================== ===================== ========================== ``area`` ``area`` ``ds * ds * area`` ``perimeter`` ``perimeter`` ``ds * perimeter`` ``axis_major_length`` ``axis_major_length`` ``ds * axis_major_length`` ``axis_minor_length`` ``axis_minor_length`` ``ds * axis_minor_length`` ===================== ===================== ========================== Parameters ---------- data Simulation data. ds Spatial resolution in microns/voxel, use None to estimate from keys. dt Temporal resolution in hours/tick, use None to estimate from keys. regions List of regions. """ if dt is None: dt = data["KEY"].apply(estimate_temporal_resolution) if ds is None: ds = data["KEY"].apply(estimate_spatial_resolution) convert_temporal_units(data, dt) convert_spatial_units(data, ds) if regions is None: return if isinstance(regions, str): regions = [regions] for region in regions: if region == "DEFAULT": continue convert_spatial_units(data, ds, region)
[docs]def convert_temporal_units(data: pd.DataFrame, dt: float) -> None: """ Convert temporal data from simulation units to true units. Simulations use temporal unit of ticks. Temporal resolution (hours/tick) is used to convert data to true units. The following temporal columns are converted: ============= ================ ============= Target column Source column(s) Conversion ============= ================ ============= ``time`` ``TICK`` ``dt * TICK`` ============= ================ ============= Parameters ---------- data Simulation data. dt Temporal resolution in hours/tick. """ if "TICK" in data.columns: data["time"] = round(dt * data["TICK"], 2)
[docs]def convert_spatial_units(data: pd.DataFrame, ds: float, region: str | None = None) -> None: """ Convert spatial data from simulation units to true units. Simulations use spatial unit of voxels. Spatial resolution (microns/voxel) is used to convert data to true units. The following spatial columns are converted: ===================== ===================== ============================= Target column Source column(s) Conversion ===================== ===================== ============================= ``volume`` ``NUM_VOXELS`` ``ds * ds * ds * NUM_VOXELS`` ``height`` ``MAX_Z`` ``MIN_Z`` ``ds * (MAX_Z - MIN_Z + 1)`` ``cx`` ``CENTER_X`` ``ds * CENTER_X`` ``cy`` ``CENTER_Y`` ``ds * CENTER_Y`` ``cz`` ``CENTER_Z`` ``ds * CENTER_Z`` ``area`` ``area`` ``ds * ds * area`` ``perimeter`` ``perimeter`` ``ds * perimeter`` ``axis_major_length`` ``axis_major_length`` ``ds * axis_major_length`` ``axis_minor_length`` ``axis_minor_length`` ``ds * axis_minor_length`` ===================== ===================== ============================= Note that the centroid columns (``cx``, ``cy``, and ``cz``) are only converted for the entire cell (``region == None``). Parameters ---------- data Simulation data. ds Spatial resolution in microns/voxel. region Name of region. """ suffix = "" if region is None else f".{region}" if f"NUM_VOXELS{suffix}" in data.columns: data[f"volume{suffix}"] = ds * ds * ds * data[f"NUM_VOXELS{suffix}"] if f"MAX_Z{suffix}" in data.columns and f"MIN_Z{suffix}" in data.columns: data[f"height{suffix}"] = ds * (data[f"MAX_Z{suffix}"] - data[f"MIN_Z{suffix}"] + 1) if "CENTER_X" in data.columns and region is None: data["cx"] = ds * data["CENTER_X"] if "CENTER_Y" in data.columns and region is None: data["cy"] = ds * data["CENTER_Y"] if "CENTER_Z" in data.columns and region is None: data["cz"] = ds * data["CENTER_Z"] property_conversions = [ ("area", ds * ds), ("perimeter", ds), ("axis_major_length", ds), ("axis_minor_length", ds), ] for name, conversion in property_conversions: column = f"{name}{suffix}" if column not in data.columns: continue data[column] = data[column] * conversion
[docs]def estimate_temporal_resolution(key: str) -> float: """ Estimate temporal resolution based on condition key. If the key contains ``DT##``, where ``##`` denotes the temporal resolution in minutes/tick, temporal resolution is estimated from ``##``. Otherwise, the default temporal resolution is 1 hours/tick. Parameters ---------- key Condition key. Returns ------- : Temporal resolution (hours/tick). """ matches = [re.fullmatch(r"DT([0-9]+)", k) for k in key.split("_")] return next((float(match.group(1)) / 60 for match in matches if match is not None), 1.0)
[docs]def estimate_spatial_resolution(key: str) -> float: """ Estimate spatial resolution based on condition key. If the key contains ``DS##``, where ``##`` denotes the spatial resolution in micron/voxel, spatial resolution is estimated from ``##``. Otherwise, the default spatial resolution is 1 micron/voxel. Parameters ---------- key Condition key. Returns ------- : Spatial resolution (micron/voxel). """ matches = [re.fullmatch(r"DS([0-9]+)", k) for k in key.split("_")] return next((float(match.group(1)) for match in matches if match is not None), 1.0)