ultralytics 8.2.99 faster JSONDict settings (#16427)

Signed-off-by: UltralyticsAssistant <web@ultralytics.com>
Co-authored-by: UltralyticsAssistant <web@ultralytics.com>
This commit is contained in:
Glenn Jocher 2024-09-22 22:38:35 +02:00 committed by GitHub
parent f5a60c6340
commit 43726d699f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 64 additions and 51 deletions

View file

@ -802,7 +802,7 @@ IS_RASPBERRYPI = is_raspberrypi()
GIT_DIR = get_git_dir()
IS_GIT_DIR = is_git_dir()
USER_CONFIG_DIR = Path(os.getenv("YOLO_CONFIG_DIR") or get_user_config_dir()) # Ultralytics settings dir
SETTINGS_YAML = USER_CONFIG_DIR / "settings.yaml"
SETTINGS_FILE = USER_CONFIG_DIR / "settings.json"
def colorstr(*input):
@ -1108,6 +1108,10 @@ class JSONDict(dict):
super().__delitem__(key)
self._save()
def __str__(self):
"""Return a pretty-printed JSON string representation of the dictionary."""
return f'JSONDict("{self.file_path}"):\n{json.dumps(dict(self), indent=2, ensure_ascii=False)}'
def update(self, *args, **kwargs):
"""Update the dictionary and persist changes."""
with self.lock:
@ -1121,21 +1125,36 @@ class JSONDict(dict):
self._save()
class SettingsManager(dict):
class SettingsManager(JSONDict):
"""
Manages Ultralytics settings stored in a YAML file.
SettingsManager class for managing and persisting Ultralytics settings.
Args:
file (str | Path): Path to the Ultralytics settings YAML file. Default is USER_CONFIG_DIR / 'settings.yaml'.
version (str): Settings version. In case of local version mismatch, new default settings will be saved.
This class extends JSONDict to provide JSON persistence for settings, ensuring thread-safe operations and default
values. It validates settings on initialization and provides methods to update or reset settings.
Attributes:
file (Path): The path to the JSON file used for persistence.
version (str): The version of the settings schema.
defaults (Dict): A dictionary containing default settings.
help_msg (str): A help message for users on how to view and update settings.
Methods:
_validate_settings: Validates the current settings and resets if necessary.
update: Updates settings, validating keys and types.
reset: Resets the settings to default and saves them.
Examples:
Initialize and update settings:
>>> settings = SettingsManager()
>>> settings.update(runs_dir="/new/runs/dir")
>>> print(settings["runs_dir"])
/new/runs/dir
"""
def __init__(self, file=SETTINGS_YAML, version="0.0.5"):
def __init__(self, file=SETTINGS_FILE, version="0.0.6"):
"""Initializes the SettingsManager with default settings and loads user settings."""
import copy
import hashlib
from ultralytics.utils.checks import check_version
from ultralytics.utils.torch_utils import torch_distributed_zero_first
root = GIT_DIR or Path()
@ -1164,45 +1183,42 @@ class SettingsManager(dict):
"vscode_msg": True,
}
self.help_msg = (
f"\nView settings with 'yolo settings' or at '{self.file}'"
"\nUpdate settings with 'yolo settings key=value', i.e. 'yolo settings runs_dir=path/to/dir'. "
f"\nView Ultralytics Settings with 'yolo settings' or at '{self.file}'"
"\nUpdate Settings with 'yolo settings key=value', i.e. 'yolo settings runs_dir=path/to/dir'. "
"For help see https://docs.ultralytics.com/quickstart/#ultralytics-settings."
)
super().__init__(copy.deepcopy(self.defaults))
with torch_distributed_zero_first(RANK):
if not self.file.exists():
self.save()
super().__init__(self.file)
self.load()
correct_keys = self.keys() == self.defaults.keys()
correct_types = all(type(a) is type(b) for a, b in zip(self.values(), self.defaults.values()))
correct_version = check_version(self["settings_version"], self.version)
if not (correct_keys and correct_types and correct_version):
LOGGER.warning(
"WARNING ⚠️ Ultralytics settings reset to default values. This may be due to a possible problem "
f"with your settings or a recent ultralytics package update. {self.help_msg}"
)
if not self.file.exists() or not self: # Check if file doesn't exist or is empty
LOGGER.info(f"Creating new Ultralytics Settings v{version} file ✅ {self.help_msg}")
self.reset()
if self.get("datasets_dir") == self.get("runs_dir"):
LOGGER.warning(
f"WARNING ⚠️ Ultralytics setting 'datasets_dir: {self.get('datasets_dir')}' "
f"must be different than 'runs_dir: {self.get('runs_dir')}'. "
f"Please change one to avoid possible issues during training. {self.help_msg}"
)
self._validate_settings()
def load(self):
"""Loads settings from the YAML file."""
super().update(yaml_load(self.file))
def _validate_settings(self):
"""Validate the current settings and reset if necessary."""
correct_keys = set(self.keys()) == set(self.defaults.keys())
correct_types = all(isinstance(self.get(k), type(v)) for k, v in self.defaults.items())
correct_version = self.get("settings_version", "") == self.version
def save(self):
"""Saves the current settings to the YAML file."""
yaml_save(self.file, dict(self))
if not (correct_keys and correct_types and correct_version):
LOGGER.warning(
"WARNING ⚠️ Ultralytics settings reset to default values. This may be due to a possible problem "
f"with your settings or a recent ultralytics package update. {self.help_msg}"
)
self.reset()
if self.get("datasets_dir") == self.get("runs_dir"):
LOGGER.warning(
f"WARNING ⚠️ Ultralytics setting 'datasets_dir: {self.get('datasets_dir')}' "
f"must be different than 'runs_dir: {self.get('runs_dir')}'. "
f"Please change one to avoid possible issues during training. {self.help_msg}"
)
def update(self, *args, **kwargs):
"""Updates a setting value in the current settings."""
"""Updates settings, validating keys and types."""
for k, v in kwargs.items():
if k not in self.defaults:
raise KeyError(f"No Ultralytics setting '{k}'. {self.help_msg}")
@ -1210,20 +1226,16 @@ class SettingsManager(dict):
if not isinstance(v, t):
raise TypeError(f"Ultralytics setting '{k}' must be of type '{t}', not '{type(v)}'. {self.help_msg}")
super().update(*args, **kwargs)
self.save()
def reset(self):
"""Resets the settings to default and saves them."""
self.clear()
self.update(self.defaults)
self.save()
def deprecation_warn(arg, new_arg):
"""Issue a deprecation warning when a deprecated argument is used, suggesting an updated argument."""
LOGGER.warning(
f"WARNING ⚠️ '{arg}' is deprecated and will be removed in in the future. " f"Please use '{new_arg}' instead."
)
LOGGER.warning(f"WARNING ⚠️ '{arg}' is deprecated and will be removed in in the future. Use '{new_arg}' instead.")
def clean_url(url):