Spaces:
Running on L40S
Running on L40S
| # SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. | |
| # SPDX-License-Identifier: OpenMDW-1.1 | |
| import base64 | |
| import binascii | |
| import itertools | |
| import json | |
| import os | |
| from abc import ABC, abstractmethod | |
| from io import BytesIO | |
| from typing import Any | |
| # Sentinel value to indicate that no default was explicitly set by the user | |
| # we want to mimic usage of function parameters: if no default is provided, the parameter is mandatory | |
| _UNSET = object() | |
| # from https://docs.python.org/3/howto/descriptor.html#validator-class | |
| # validators can be customized to very specific needs, e.g. see HumanAttributes below | |
| class Validator(ABC): | |
| def __init__(self, default=_UNSET, hidden=False): | |
| self.default = default | |
| self.hidden = hidden | |
| # set name is called when the validator is created as class variable | |
| # name is the name of the variable in the owner class, so here we create the name for the backing variable | |
| def __set_name__(self, owner, name): | |
| self.private_name = "_" + name | |
| def __get__(self, obj, objtype=None): | |
| value = getattr(obj, self.private_name, self.default) | |
| if value is _UNSET: | |
| # If we reach here, it means a mandatory parameter was accessed without being set | |
| attr_name = getattr(self, "private_name", "unknown").lstrip("_") | |
| raise ValueError( | |
| f"Parameter '{attr_name}' is mandatory but has not been set. " | |
| f"No default value was provided and no value was assigned." | |
| ) | |
| return value | |
| def __set__(self, obj, value): | |
| value = self.validate(value) | |
| setattr(obj, self.private_name, value) | |
| def validate(self, value): | |
| pass | |
| def json(self): | |
| pass | |
| class Bool(Validator): | |
| def __init__(self, default=_UNSET, hidden=False, tooltip=None): | |
| super().__init__(default, hidden) | |
| self.default = default | |
| self.hidden = hidden | |
| self.tooltip = tooltip | |
| def validate(self, value): | |
| if isinstance(value, int): | |
| value = value != 0 | |
| elif isinstance(value, str): | |
| value = value.lower() | |
| if value in ["true", "1"]: | |
| value = True | |
| elif value in ["false", "0"]: | |
| value = False | |
| else: | |
| raise ValueError(f"Expected {value!r} to be one of ['True', 'False', '1', '0']") | |
| elif not isinstance(value, bool): | |
| raise TypeError(f"Expected {value!r} to be an bool") | |
| return value | |
| def get_range_iterator(self): | |
| return [True, False] | |
| def __repr__(self) -> str: | |
| return f"Bool({self.private_name=} {self.default=} {self.hidden=})" | |
| def json(self): | |
| return { | |
| "type": bool.__name__, | |
| "default": self.default, | |
| "tooltip": self.tooltip, | |
| } | |
| class Int(Validator): | |
| def __init__(self, default=_UNSET, min=None, max=None, step=1, hidden=False, tooltip=None): | |
| self.min = min | |
| self.max = max | |
| self.default = default | |
| self.step = step | |
| self.hidden = hidden | |
| self.tooltip = tooltip | |
| def validate(self, value): | |
| if isinstance(value, str): | |
| value = int(value) | |
| elif not isinstance(value, int): | |
| raise TypeError(f"Expected {value!r} to be an int") | |
| if self.min is not None and value < self.min: | |
| raise ValueError(f"Expected {value!r} to be at least {self.min!r}") | |
| if self.max is not None and value > self.max: | |
| raise ValueError(f"Expected {value!r} to be no more than {self.max!r}") | |
| return value | |
| def get_range_iterator(self): | |
| if self.default is _UNSET: | |
| default_val = 0 | |
| else: | |
| default_val = int(self.default) if isinstance(self.default, (int, float, str)) else 0 | |
| iter_min = self.min if self.min is not None else default_val | |
| iter_max = self.max if self.max is not None else (default_val + 100) | |
| return itertools.takewhile(lambda x: x <= iter_max, itertools.count(iter_min, self.step)) | |
| def __repr__(self) -> str: | |
| return f"Int({self.private_name=} {self.default=}, {self.min=}, {self.max=} {self.hidden=})" | |
| def json(self): | |
| return { | |
| "type": int.__name__, | |
| "default": self.default, | |
| "min": self.min, | |
| "max": self.max, | |
| "step": self.step, | |
| "tooltip": self.tooltip, | |
| } | |
| class Float(Validator): | |
| def __init__(self, default=_UNSET, min=None, max=None, step=0.5, hidden=False, tooltip=None): | |
| self.min = min | |
| self.max = max | |
| self.default = default | |
| self.step = step | |
| self.hidden = hidden | |
| self.tooltip = tooltip | |
| def validate(self, value): | |
| if isinstance(value, str) or isinstance(value, int): | |
| value = float(value) | |
| elif not isinstance(value, float): | |
| raise TypeError(f"Expected {value!r} to be float") | |
| if self.min is not None and value < self.min: | |
| raise ValueError(f"Expected {value!r} to be at least {self.min!r}") | |
| if self.max is not None and value > self.max: | |
| raise ValueError(f"Expected {value!r} to be no more than {self.max!r}") | |
| return value | |
| def get_range_iterator(self): | |
| if self.default is _UNSET: | |
| default_val = 0.0 | |
| else: | |
| default_val = float(self.default) if isinstance(self.default, (int, float, str)) else 0.0 | |
| iter_min = self.min if self.min is not None else default_val | |
| iter_max = self.max if self.max is not None else (default_val + 100.0) | |
| return itertools.takewhile(lambda x: x <= iter_max, itertools.count(iter_min, self.step)) | |
| def __repr__(self) -> str: | |
| return f"Float({self.private_name=} {self.default=}, {self.min=}, {self.max=} {self.hidden=})" | |
| def json(self): | |
| return { | |
| "type": float.__name__, | |
| "default": self.default, | |
| "min": self.min, | |
| "max": self.max, | |
| "step": self.step, | |
| "tooltip": self.tooltip, | |
| } | |
| class String(Validator): | |
| def __init__(self, default=_UNSET, min=None, max=None, predicate=None, hidden=False, tooltip=None): | |
| self.min = min | |
| self.max = max | |
| self.predicate = predicate | |
| self.default = default | |
| self.hidden = hidden | |
| self.tooltip = tooltip | |
| def validate(self, value): | |
| if value is None: | |
| return value # Allow None as a valid value to be compatible with existing code | |
| # this breaks strict typing, so do this only for strings | |
| if not isinstance(value, str): | |
| raise TypeError(f"Expected {value!r} to be an str or None") | |
| if self.min is not None and len(value) < self.min: | |
| raise ValueError(f"Expected {value!r} to be no smaller than {self.min!r}") | |
| if self.max is not None and len(value) > self.max: | |
| raise ValueError(f"Expected {value!r} to be no bigger than {self.max!r}") | |
| if self.predicate is not None and not self.predicate(value): | |
| raise ValueError(f"Expected {self.predicate} to be true for {value!r}") | |
| return value | |
| def get_range_iterator(self): | |
| return iter([self.default]) | |
| def __repr__(self) -> str: | |
| return f"String({self.private_name=} {self.default=}, {self.min=}, {self.max=} {self.hidden=})" | |
| def json(self): | |
| return { | |
| "type": str.__name__, | |
| "default": self.default, | |
| "tooltip": self.tooltip, | |
| } | |
| class Path(Validator): | |
| def __init__(self, default=_UNSET, hidden=False, tooltip=None): | |
| self.default = default | |
| self.hidden = hidden | |
| self.tooltip = tooltip | |
| def validate(self, value): | |
| if value is None: | |
| return value | |
| if not isinstance(value, str): | |
| raise TypeError(f"{self.private_name} validator: Expected {value!r} to be an str") | |
| if not os.path.exists(value): | |
| raise ValueError(f"{self.private_name} validator: Expected {value!r} to be a valid path") | |
| return value | |
| def get_range_iterator(self): | |
| return iter([self.default]) | |
| def __repr__(self) -> str: | |
| return f"String({self.private_name=} {self.default=}, {self.hidden=})" | |
| class InputImage(Validator): | |
| def __init__( | |
| self, default=_UNSET, hidden=False, tooltip=None, supported_formats=["jpeg", "jpg", "png", "bmp", "gif"] | |
| ): | |
| self.default = default | |
| self.hidden = hidden | |
| self.tooltip = tooltip | |
| self.supported_formats = supported_formats | |
| def validate(self, value): | |
| ext = os.path.splitext(value)[1].lower() | |
| if ext not in self.supported_formats: | |
| raise ValueError(f"Unsupported image format: {ext}") | |
| if not isinstance(value, str): | |
| raise TypeError(f"Expected {value!r} to be an str") | |
| if not os.path.exists(value): | |
| raise ValueError(f"Expected {value!r} to be a valid path") | |
| return value | |
| def get_range_iterator(self): | |
| return iter([self.default]) | |
| def __repr__(self) -> str: | |
| return f"String({self.private_name=} {self.default=} {self.hidden=})" | |
| def json(self): | |
| return { | |
| "type": InputImage.__name__, | |
| "default": self.default, | |
| "values": self.supported_formats, | |
| "tooltip": self.tooltip, | |
| } | |
| class JsonDict(Validator): | |
| """ | |
| JSON stringified version of a python dict. | |
| Example: '{"ema_customization_iter.pt": "ema_customization_iter.pt"}' | |
| """ | |
| def __init__(self, default=_UNSET, hidden=False): | |
| self.default = default | |
| self.hidden = hidden | |
| def validate(self, value): | |
| if not value: | |
| return {} | |
| try: | |
| dict = json.loads(value) | |
| return dict | |
| except json.JSONDecodeError as e: | |
| raise ValueError(f"Expected {value!r} to be json stringified dict. Error: {str(e)}") | |
| def __repr__(self) -> str: | |
| return f"Dict({self.default=} {self.hidden=})" | |
| class Dict(Validator): | |
| """ | |
| Python dict. | |
| Example: {'key': 'value'} | |
| This allows a single level of parameter nesting, but not a full nested dict. | |
| For now we validate the individual keys here and store the dict as is. | |
| Alternatively, we could have a validator that gets/sets another ValidatorParams class. | |
| """ | |
| def __init__(self, default=_UNSET, hidden=False): | |
| self.default = default | |
| self.hidden = hidden | |
| def validate(self, value): | |
| if not isinstance(value, dict): | |
| raise TypeError(f"Expected {value!r} to be an dict") | |
| return value | |
| def __repr__(self) -> str: | |
| value = getattr(self, self.private_name, self.default) | |
| return f"Dict({self.private_name=} {self.default=} {self.hidden=} value={json.dumps(value, indent=4)})" | |
| class OneOf(Validator): | |
| def __init__(self, default=_UNSET, options=None, type_cast=None, hidden=False, tooltip=None): | |
| self.options = set(options) if options is not None else set() | |
| self.default = default | |
| self.type_cast = type_cast # Cast the value to this type before checking if it's in options | |
| self.tooltip = tooltip | |
| self.hidden = hidden | |
| def validate(self, value): | |
| if self.type_cast: | |
| try: | |
| value = self.type_cast(value) | |
| except ValueError: | |
| raise ValueError(f"Expected {value!r} to be castable to {self.type_cast!r}") | |
| if value not in self.options: | |
| raise ValueError(f"Expected {value!r} to be one of {self.options!r}") | |
| return value | |
| def get_range_iterator(self): | |
| return self.options | |
| def __repr__(self) -> str: | |
| return f"OneOf({self.private_name=} {self.options=} {self.hidden=})" | |
| def json(self): | |
| return { | |
| "type": OneOf.__name__, | |
| "default": self.default, | |
| "values": list(self.options), | |
| "tooltip": self.tooltip, | |
| } | |
| class MultipleOf(Validator): | |
| def __init__(self, default=_UNSET, multiple_of: int = 1, type_cast=None, hidden=False, tooltip=None): | |
| if type(multiple_of) is not int: | |
| raise ValueError(f"Expected {multiple_of!r} to be an int") | |
| self.multiple_of = multiple_of | |
| self.default = default | |
| self.type_cast = type_cast | |
| # if a parameter is hidden then probe() can't expose the param | |
| # and the param can't be set anymore | |
| self.hidden = hidden | |
| self.tooltip = tooltip | |
| def validate(self, value): | |
| if self.type_cast: | |
| try: | |
| value = self.type_cast(value) | |
| except ValueError: | |
| raise ValueError(f"Expected {value!r} to be castable to {self.type_cast!r}") | |
| if value % self.multiple_of != 0: | |
| raise ValueError(f"Expected {value!r} to be a multiple of {self.multiple_of!r}") | |
| return value | |
| def get_range_iterator(self): | |
| return itertools.count(0, self.multiple_of) | |
| def __repr__(self) -> str: | |
| return f"MultipleOf({self.private_name=} {self.multiple_of=} {self.hidden=})" | |
| def json(self): | |
| return { | |
| "type": MultipleOf.__name__, | |
| "default": self.default, | |
| "multiple_of": self.multiple_of, | |
| "tooltip": self.tooltip, | |
| } | |
| class HumanAttributes(Validator): | |
| def __init__(self, default=_UNSET, hidden=False, tooltip=None): | |
| self.default = default | |
| self.hidden = hidden | |
| self.tooltip = tooltip | |
| # hard code the options for now | |
| # we extend this to init parameter as needed | |
| valid_attributes = { | |
| "emotion": ["angry", "contemptful", "disgusted", "fearful", "happy", "neutral", "sad", "surprised"], | |
| "race": ["asian", "indian", "black", "white", "middle eastern", "latino hispanic"], | |
| "gender": ["male", "female"], | |
| "age group": [ | |
| "young", | |
| "teen", | |
| "adult early twenties", | |
| "adult late twenties", | |
| "adult early thirties", | |
| "adult late thirties", | |
| "adult middle aged", | |
| "older adult", | |
| ], | |
| } | |
| def get_range_iterator(self): | |
| # create a list of all possible combinations | |
| l1 = self.valid_attributes["emotion"] | |
| l2 = self.valid_attributes["race"] | |
| l3 = self.valid_attributes["gender"] | |
| l4 = self.valid_attributes["age group"] | |
| all_combinations = list(itertools.product(l1, l2, l3, l4)) | |
| return iter(all_combinations) | |
| def validate(self, value): | |
| human_attributes = value.lower() | |
| if human_attributes not in ["none", "random"]: | |
| # In this case, we need for custom attribute string | |
| attr_string = human_attributes | |
| for attr_key in ["emotion", "race", "gender", "age group"]: | |
| attr_detected = False | |
| for attr_label in self.valid_attributes[attr_key]: | |
| if attr_string.startswith(attr_label): | |
| attr_string = attr_string[len(attr_label) + 1 :] # noqa: E203 | |
| attr_detected = True | |
| break | |
| if attr_detected is False: | |
| raise ValueError(f"Expected {value!r} to be one of {self.valid_attributes!r}") | |
| return value | |
| def __repr__(self) -> str: | |
| return f"HumanAttributes({self.private_name=} {self.hidden=})" | |
| def json(self): | |
| return { | |
| "type": HumanAttributes.__name__, | |
| "default": self.default, | |
| "values": self.valid_attributes, | |
| "tooltip": self.tooltip, | |
| } | |
| class BytesIOType(Validator): | |
| """ | |
| Validator class for BytesIO. Valid inputs are either: | |
| - bytes | |
| - objects of class BytesIO | |
| - str which can be successfully decoded into BytesIO | |
| """ | |
| def __init__(self, default=_UNSET, hidden=False, tooltip=None): | |
| self.default = default | |
| self.hidden = hidden | |
| self.tooltip = tooltip | |
| def validate(self, value: Any) -> BytesIO: | |
| if isinstance(value, str): | |
| try: | |
| # Decode the Base64 string | |
| decoded_bytes = base64.b64decode(value) | |
| # Create a BytesIO stream from the decoded bytes | |
| return BytesIO(decoded_bytes) | |
| except (binascii.Error, ValueError) as e: | |
| raise ValueError(f"Invalid Base64 encoded string: {e}") | |
| elif isinstance(value, bytes): | |
| return BytesIO(value) | |
| elif isinstance(value, BytesIO): | |
| return value | |
| else: | |
| raise TypeError(f"Expected {value!r} to be a Base64 encoded string, bytes, or BytesIO") | |
| def __repr__(self) -> str: | |
| return f"BytesIOValidator({self.default=}, {self.hidden=})" | |
| def json(self): | |
| return { | |
| "type": BytesIO.__name__, | |
| "default": self.default, | |
| "tooltip": self.tooltip, | |
| } | |