|
1 | 1 | """Configuration module for graphomotor.""" |
2 | 2 |
|
3 | 3 | import dataclasses |
| 4 | +import json |
4 | 5 | import logging |
| 6 | +import os |
5 | 7 | import typing |
6 | 8 | from importlib import metadata |
| 9 | +from typing import Dict |
7 | 10 |
|
8 | 11 | import numpy as np |
9 | 12 |
|
| 13 | +from graphomotor.core import models |
| 14 | + |
10 | 15 |
|
11 | 16 | def get_version() -> str: |
12 | 17 | """Return graphomotor version.""" |
@@ -93,3 +98,76 @@ def add_custom_params(cls, config_dict: dict[str, float | int]) -> "SpiralConfig |
93 | 98 | ) |
94 | 99 |
|
95 | 100 | return cls(**filtered_params) |
| 101 | + |
| 102 | + |
| 103 | +def load_scaled_circles(filepath: str) -> Dict[str, Dict[str, models.CircleTarget]]: |
| 104 | + """Load circle configurations from trails_points_scaled.json. |
| 105 | +
|
| 106 | + This function reads a JSON file containing circle target definitions for various |
| 107 | + trail types and constructs CircleTarget instances for each defined circle. |
| 108 | + This will be configured only once per run. |
| 109 | +
|
| 110 | + Args: |
| 111 | + filepath: Path to the JSON file containing circle configurations. |
| 112 | +
|
| 113 | + Returns: |
| 114 | + A dictionary mapping each trail type to dictionaries of CircleTarget instances. |
| 115 | +
|
| 116 | + Raises: |
| 117 | + FileNotFoundError: If the specified filepath does not exist. |
| 118 | + json.JSONDecodeError: If the file contains invalid JSON. |
| 119 | + KeyError: If required fields (x, y, label, radius) are missing from circle data. |
| 120 | + TypeError: If trails_data is not a dictionary or trail_points is not a list. |
| 121 | + """ |
| 122 | + if not os.path.exists(filepath): |
| 123 | + raise FileNotFoundError( |
| 124 | + f"The path '{filepath}' does not exist. " |
| 125 | + "Confirm that all necessary files are in place." |
| 126 | + ) |
| 127 | + |
| 128 | + with open(filepath, "r") as f: |
| 129 | + trails_data = json.load(f) |
| 130 | + |
| 131 | + if not isinstance(trails_data, dict): |
| 132 | + raise TypeError( |
| 133 | + f"Expected trails_data to be a dictionary, got {type(trails_data).__name__}" |
| 134 | + ) |
| 135 | + |
| 136 | + circles = {} |
| 137 | + required_fields = ["x", "y", "label", "radius"] |
| 138 | + for trail_task_number, trail_points in trails_data.items(): |
| 139 | + if not isinstance(trail_points, list): |
| 140 | + raise TypeError( |
| 141 | + f"Expected trail_points for '{trail_task_number}' to be a list, " |
| 142 | + f"got {type(trail_points).__name__}" |
| 143 | + ) |
| 144 | + |
| 145 | + trail_circles = {} |
| 146 | + for idx, point_dict in enumerate(trail_points): |
| 147 | + if not isinstance(point_dict, dict): |
| 148 | + raise TypeError( |
| 149 | + f"Expected point at index {idx} in '{trail_task_number}' to be a " |
| 150 | + f"dictionary, got {type(point_dict).__name__}" |
| 151 | + ) |
| 152 | + |
| 153 | + missing_fields = [ |
| 154 | + field for field in required_fields if field not in point_dict |
| 155 | + ] |
| 156 | + if missing_fields: |
| 157 | + raise KeyError( |
| 158 | + f"Missing required field(s) {missing_fields} at index {idx} " |
| 159 | + f"of trail '{trail_task_number}'" |
| 160 | + ) |
| 161 | + |
| 162 | + order = idx + 1 |
| 163 | + circle = models.CircleTarget( |
| 164 | + order=order, |
| 165 | + center_x=point_dict["x"], |
| 166 | + center_y=point_dict["y"], |
| 167 | + label=str(point_dict["label"]), |
| 168 | + radius=point_dict["radius"], |
| 169 | + ) |
| 170 | + trail_circles[circle.label] = circle |
| 171 | + |
| 172 | + circles[trail_task_number] = trail_circles |
| 173 | + return circles |
0 commit comments