Coverage for src/arcade_collection/input/generate_setup_file.py: 100%
30 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 xml.etree.ElementTree as ET
4from typing import TYPE_CHECKING
6import numpy as np
8if TYPE_CHECKING:
9 import pandas as pd
11DEFAULT_POPULATION_ID = "X"
12"""Default population ID used in setup file."""
15def generate_setup_file(
16 samples: pd.DataFrame, margins: tuple[int, int, int], terms: list[str]
17) -> str:
18 """
19 Create ARCADE setup file from samples, margins, and CPM Hamiltonian terms.
21 Initial number of cells is determined by number of unique ids in samples.
22 Regions are included if samples contains valid regions.
24 Parameters
25 ----------
26 samples
27 Sample cell ids and coordinates.
28 margins
29 Margin size in x, y, and z directions.
30 terms
31 List of Potts Hamiltonian terms for setup file.
33 Returns
34 -------
35 :
36 Contents of ARCADE setup file.
37 """
39 init = len(samples["id"].unique())
40 bounds = calculate_sample_bounds(samples, margins)
41 regions = (
42 samples["region"].unique()
43 if "region" in samples.columns and not samples["region"].isna().all()
44 else None
45 )
46 return make_setup_file(init, bounds, terms, regions)
49def calculate_sample_bounds(
50 samples: pd.DataFrame, margins: tuple[int, int, int]
51) -> tuple[int, int, int]:
52 """
53 Calculate transformed sample bounds including margin.
55 Parameters
56 ----------
57 samples
58 Sample cell ids and coordinates.
59 margins
60 Margin size in x, y, and z directions.
62 Returns
63 -------
64 :
65 Bounds in x, y, and z directions.
66 """
68 mins = (min(samples.x), min(samples.y), min(samples.z))
69 maxs = (max(samples.x), max(samples.y), max(samples.z))
71 bound_x, bound_y, bound_z = np.subtract(maxs, mins) + np.multiply(2, margins) + 3
73 return (bound_x, bound_y, bound_z)
76def make_setup_file(
77 init: int,
78 bounds: tuple[int, int, int],
79 terms: list[str],
80 regions: list[str] | None = None,
81) -> str:
82 """
83 Create ARCADE setup file.
85 Parameters
86 ----------
87 init
88 Number of initial cells.
89 bounds
90 Bounds in x, y, and z directions.
91 regions
92 List of regions.
93 terms
94 List of Potts Hamiltonian terms for setup file.
96 Returns
97 -------
98 :
99 Contents of ARCADE setup file.
100 """
102 root = ET.fromstring("<set></set>") # noqa: S314
103 series = ET.SubElement(
104 root,
105 "series",
106 {
107 "name": "ARCADE",
108 "interval": "1",
109 "start": "0",
110 "end": "0",
111 "dt": "1",
112 "ds": "1",
113 "ticks": "1",
114 "length": str(int(bounds[0])),
115 "width": str(int(bounds[1])),
116 "height": str(int(bounds[2])),
117 },
118 )
120 potts = ET.SubElement(series, "potts")
121 for term in terms:
122 ET.SubElement(potts, "potts.term", {"id": term})
124 agents = ET.SubElement(series, "agents")
125 populations = ET.SubElement(agents, "populations")
126 population = ET.SubElement(populations, "population", {"id": "X", "init": str(init)})
128 if regions is not None:
129 for region in regions:
130 ET.SubElement(population, "population.region", {"id": region})
132 ET.indent(root, space=" ", level=0)
133 return ET.tostring(root, encoding="unicode")