Files
design2garmentcode-impl/pygarment/garmentcode/params.py

153 lines
4.8 KiB
Python
Raw Normal View History

2025-07-03 17:03:00 +08:00
"""Parameter class wrappers around parameter files allowing definition of computed parameters
"""
import yaml
from pathlib import Path
from copy import deepcopy
import random
from pygarment.garmentcode.utils import nested_get, nested_set, close_enough
class BodyParametrizationBase:
"""Base class for body parametrization wrappers that allows definition of
dependent parameters
"""
def __init__(self, param_file='') -> None:
self.params = {}
self.load(param_file)
def __getitem__(self, key):
return self.params[key]
def __iter__(self):
"""
Return an iterator of dict keys
"""
return iter(self.params)
# Updates
def __setitem__(self, key, value):
self.params[key] = value
self.eval_dependencies(key)
def load(self, param_file):
"""Load new values from file"""
with open(param_file, 'r') as f:
dict = yaml.safe_load(f)['body']
self.params.update(dict)
self.eval_dependencies() # Parameters have been updated
def load_from_dict(self, in_dict):
self.params.update(in_dict)
self.eval_dependencies() # Parameters have been updated
# Processing
def eval_dependencies(self, key=None):
"""Evaluate dependent attributes, e.g. after a new value has been set
Define your dependent parameters in the overload of this function
* key -- the information on what field is being updated
"""
pass
# Save
def save(self, path, name='body_measurements'):
with open(Path(path) / f'{name}.yaml', 'w') as f:
yaml.dump(
{'body': self.params},
f,
default_flow_style=False
)
class DesignSampler:
"""Base class for design parameters sampling """
def __init__(self, param_file='') -> None:
self.params = {}
if param_file:
self.load(param_file)
def load(self, param_file):
"""Load new values from file"""
with open(param_file, 'r') as f:
dict = yaml.safe_load(f)['design']
self.params.update(dict)
def default(self):
return self.params
# ---- Randomization of values ----
def randomize(self):
"""Generate random values for the current design parameters"""
random_params = deepcopy(self.params)
# NOTE dealing with the nested dict
self._randomize_subset(random_params, [])
return random_params
def _randomize_subset(self, random_params, path):
subset = nested_get(random_params, path) if path else random_params
for key in subset:
if 'v' in subset[key].keys():
self._randomize_value(random_params, path + [key])
else:
self._randomize_subset(random_params, path + [key])
def _randomize_value(self, random_params, path):
""" Randomize the value of one parameter
Path is leading to the leaf of param dict. value.
"""
range = nested_get(random_params, path + ['range'])
p_type = nested_get(random_params, path + ['type'])
# Check Defaults
try:
def_prob = nested_get(random_params, path + ['default_prob'])
except KeyError as e: # Default probability not given -> Sample uniformly
def_prob = None
def_value = nested_get(self.params, path + ['v'])
if self.__use_default(def_prob):
new_val = def_value
else:
if 'select' in p_type or p_type == 'bool' or 'file' in p_type: # All discrete types
if p_type == 'select_null' and None not in range:
range.append(None)
# Exclude default
if def_prob is not None:
range.remove(def_value)
new_val = random.choice(range)
elif p_type == 'int':
new_val = self.__randint_exclude(range, None if def_prob is None else def_value)
elif p_type == 'float':
new_val = self.__uniform_exclude(range, None if def_prob is None else def_value)
nested_set(random_params, path + ['v'], new_val)
def __use_default(self, probability):
if probability is None:
return False
return random.random() < probability
def __randint_exclude(self, range, exclude):
rand_v = random.randint(*range)
if exclude is not None and rand_v == exclude:
return self.__randint_exclude(range, exclude)
return rand_v
def __uniform_exclude(self, range, exclude):
rand_v = random.uniform(*range)
if exclude is not None and close_enough(rand_v, exclude):
return self.__uniform_exclude(range, exclude)
return rand_v