Coverage for src/arcade_collection/output/convert_model_units.py: 100%
45 statements
« prev ^ index » next coverage.py v7.1.0, created at 2024-12-09 19:07 +0000
« prev ^ index » next coverage.py v7.1.0, created at 2024-12-09 19:07 +0000
1from __future__ import annotations
3import re
4from typing import TYPE_CHECKING
6if TYPE_CHECKING:
7 import pandas as pd
10def convert_model_units(
11 data: pd.DataFrame,
12 ds: float | None,
13 dt: float | None,
14 regions: list[str] | str | None = None,
15) -> None:
16 """
17 Convert data from simulation units to true units.
19 Simulations use spatial unit of voxels and temporal unit of ticks. Spatial
20 resolution (microns/voxel) and temporal resolution (hours/tick) are used to
21 convert data to true units. If spatial or temporal resolution is not given,
22 they will be estimated from the ``KEY`` column of the data.
24 The following columns are added to the data:
26 ============= =================== =============================
27 Target column Source column(s) Conversion
28 ============= =================== =============================
29 ``time`` ``TICK`` ``dt * TICK``
30 ``volume`` ``NUM_VOXELS`` ``ds * ds * ds * NUM_VOXELS``
31 ``height`` ``MAX_Z`` ``MIN_Z`` ``ds * (MAX_Z - MIN_Z + 1)``
32 ``cx`` ``CENTER_X`` ``ds * CENTER_X``
33 ``cy`` ``CENTER_Y`` ``ds * CENTER_Y``
34 ``cz`` ``CENTER_Z`` ``ds * CENTER_Z``
35 ============= =================== =============================
37 For each region (other than ``DEFAULT``), the following columns are added to the data:
39 ================= ================================= ==========================================
40 Target column Source column(s) Conversion
41 ================= ================================= ==========================================
42 ``volume.REGION`` ``NUM_VOXELS.REGION`` ``ds * ds * ds * NUM_VOXELS.REGION``
43 ``height.REGION`` ``MAX_Z.REGION`` ``MIN_Z.REGION`` ``ds * (MAX_Z.REGION - MIN_Z.REGION + 1)``
44 ================= ================================= ==========================================
46 The following property columns are rescaled:
48 ===================== ===================== ==========================
49 Target column Source column(s) Conversion
50 ===================== ===================== ==========================
51 ``area`` ``area`` ``ds * ds * area``
52 ``perimeter`` ``perimeter`` ``ds * perimeter``
53 ``axis_major_length`` ``axis_major_length`` ``ds * axis_major_length``
54 ``axis_minor_length`` ``axis_minor_length`` ``ds * axis_minor_length``
55 ===================== ===================== ==========================
57 Parameters
58 ----------
59 data
60 Simulation data.
61 ds
62 Spatial resolution in microns/voxel, use None to estimate from keys.
63 dt
64 Temporal resolution in hours/tick, use None to estimate from keys.
65 regions
66 List of regions.
67 """
69 if dt is None:
70 dt = data["KEY"].apply(estimate_temporal_resolution)
72 if ds is None:
73 ds = data["KEY"].apply(estimate_spatial_resolution)
75 convert_temporal_units(data, dt)
76 convert_spatial_units(data, ds)
78 if regions is None:
79 return
81 if isinstance(regions, str):
82 regions = [regions]
84 for region in regions:
85 if region == "DEFAULT":
86 continue
88 convert_spatial_units(data, ds, region)
91def convert_temporal_units(data: pd.DataFrame, dt: float) -> None:
92 """
93 Convert temporal data from simulation units to true units.
95 Simulations use temporal unit of ticks. Temporal resolution (hours/tick) is
96 used to convert data to true units.
98 The following temporal columns are converted:
100 ============= ================ =============
101 Target column Source column(s) Conversion
102 ============= ================ =============
103 ``time`` ``TICK`` ``dt * TICK``
104 ============= ================ =============
106 Parameters
107 ----------
108 data
109 Simulation data.
110 dt
111 Temporal resolution in hours/tick.
112 """
114 if "TICK" in data.columns:
115 data["time"] = round(dt * data["TICK"], 2)
118def convert_spatial_units(data: pd.DataFrame, ds: float, region: str | None = None) -> None:
119 """
120 Convert spatial data from simulation units to true units.
122 Simulations use spatial unit of voxels. Spatial resolution (microns/voxel)
123 is used to convert data to true units.
125 The following spatial columns are converted:
127 ===================== ===================== =============================
128 Target column Source column(s) Conversion
129 ===================== ===================== =============================
130 ``volume`` ``NUM_VOXELS`` ``ds * ds * ds * NUM_VOXELS``
131 ``height`` ``MAX_Z`` ``MIN_Z`` ``ds * (MAX_Z - MIN_Z + 1)``
132 ``cx`` ``CENTER_X`` ``ds * CENTER_X``
133 ``cy`` ``CENTER_Y`` ``ds * CENTER_Y``
134 ``cz`` ``CENTER_Z`` ``ds * CENTER_Z``
135 ``area`` ``area`` ``ds * ds * area``
136 ``perimeter`` ``perimeter`` ``ds * perimeter``
137 ``axis_major_length`` ``axis_major_length`` ``ds * axis_major_length``
138 ``axis_minor_length`` ``axis_minor_length`` ``ds * axis_minor_length``
139 ===================== ===================== =============================
141 Note that the centroid columns (``cx``, ``cy``, and ``cz``) are only
142 converted for the entire cell (``region == None``).
144 Parameters
145 ----------
146 data
147 Simulation data.
148 ds
149 Spatial resolution in microns/voxel.
150 region
151 Name of region.
152 """
154 suffix = "" if region is None else f".{region}"
156 if f"NUM_VOXELS{suffix}" in data.columns:
157 data[f"volume{suffix}"] = ds * ds * ds * data[f"NUM_VOXELS{suffix}"]
159 if f"MAX_Z{suffix}" in data.columns and f"MIN_Z{suffix}" in data.columns:
160 data[f"height{suffix}"] = ds * (data[f"MAX_Z{suffix}"] - data[f"MIN_Z{suffix}"] + 1)
162 if "CENTER_X" in data.columns and region is None:
163 data["cx"] = ds * data["CENTER_X"]
165 if "CENTER_Y" in data.columns and region is None:
166 data["cy"] = ds * data["CENTER_Y"]
168 if "CENTER_Z" in data.columns and region is None:
169 data["cz"] = ds * data["CENTER_Z"]
171 property_conversions = [
172 ("area", ds * ds),
173 ("perimeter", ds),
174 ("axis_major_length", ds),
175 ("axis_minor_length", ds),
176 ]
178 for name, conversion in property_conversions:
179 column = f"{name}{suffix}"
181 if column not in data.columns:
182 continue
184 data[column] = data[column] * conversion
187def estimate_temporal_resolution(key: str) -> float:
188 """
189 Estimate temporal resolution based on condition key.
191 If the key contains ``DT##``, where ``##`` denotes the temporal resolution
192 in minutes/tick, temporal resolution is estimated from ``##``. Otherwise,
193 the default temporal resolution is 1 hours/tick.
195 Parameters
196 ----------
197 key
198 Condition key.
200 Returns
201 -------
202 :
203 Temporal resolution (hours/tick).
204 """
206 matches = [re.fullmatch(r"DT([0-9]+)", k) for k in key.split("_")]
207 return next((float(match.group(1)) / 60 for match in matches if match is not None), 1.0)
210def estimate_spatial_resolution(key: str) -> float:
211 """
212 Estimate spatial resolution based on condition key.
214 If the key contains ``DS##``, where ``##`` denotes the spatial resolution
215 in micron/voxel, spatial resolution is estimated from ``##``. Otherwise,
216 the default spatial resolution is 1 micron/voxel.
218 Parameters
219 ----------
220 key
221 Condition key.
223 Returns
224 -------
225 :
226 Spatial resolution (micron/voxel).
227 """
229 matches = [re.fullmatch(r"DS([0-9]+)", k) for k in key.split("_")]
230 return next((float(match.group(1)) for match in matches if match is not None), 1.0)