CoolFace
Apppublic

breadlicker45/gpuGAN

sourceHugging Facecc-by-nc-4.0updated 8mo agoView on Hugging Face
0likes
util.py478 linesDownload Raw Back to dnnlib
1# Copyright (c) 2021, NVIDIA CORPORATION.  All rights reserved.2#3# NVIDIA CORPORATION and its licensors retain all intellectual property4# and proprietary rights in and to this software, related documentation5# and any modifications thereto.  Any use, reproduction, disclosure or6# distribution of this software and related documentation without an express7# license agreement from NVIDIA CORPORATION is strictly prohibited.8 9"""Miscellaneous utility classes and functions."""10 11import ctypes12import fnmatch13import importlib14import inspect15import numpy as np16import os17import shutil18import sys19import types20import io21import pickle22import re23import requests24import html25import hashlib26import glob27import tempfile28import urllib29import urllib.request30import uuid31 32from distutils.util import strtobool33from typing import Any, List, Tuple, Union34 35 36# Util classes37# ------------------------------------------------------------------------------------------38 39 40class EasyDict(dict):41    """Convenience class that behaves like a dict but allows access with the attribute syntax."""42 43    def __getattr__(self, name: str) -> Any:44        try:45            return self[name]46        except KeyError:47            raise AttributeError(name)48 49    def __setattr__(self, name: str, value: Any) -> None:50        self[name] = value51 52    def __delattr__(self, name: str) -> None:53        del self[name]54 55 56class Logger(object):57    """Redirect stderr to stdout, optionally print stdout to a file, and optionally force flushing on both stdout and the file."""58 59    def __init__(self, file_name: str = None, file_mode: str = "w", should_flush: bool = True):60        self.file = None61 62        if file_name is not None:63            self.file = open(file_name, file_mode)64 65        self.should_flush = should_flush66        self.stdout = sys.stdout67        self.stderr = sys.stderr68 69        sys.stdout = self70        sys.stderr = self71 72    def __enter__(self) -> "Logger":73        return self74 75    def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> None:76        self.close()77 78    def write(self, text: Union[str, bytes]) -> None:79        """Write text to stdout (and a file) and optionally flush."""80        if isinstance(text, bytes):81            text = text.decode()82        if len(text) == 0: # workaround for a bug in VSCode debugger: sys.stdout.write(''); sys.stdout.flush() => crash83            return84 85        if self.file is not None:86            self.file.write(text)87 88        self.stdout.write(text)89 90        if self.should_flush:91            self.flush()92 93    def flush(self) -> None:94        """Flush written text to both stdout and a file, if open."""95        if self.file is not None:96            self.file.flush()97 98        self.stdout.flush()99 100    def close(self) -> None:101        """Flush, close possible files, and remove stdout/stderr mirroring."""102        self.flush()103 104        # if using multiple loggers, prevent closing in wrong order105        if sys.stdout is self:106            sys.stdout = self.stdout107        if sys.stderr is self:108            sys.stderr = self.stderr109 110        if self.file is not None:111            self.file.close()112            self.file = None113 114 115# Cache directories116# ------------------------------------------------------------------------------------------117 118_dnnlib_cache_dir = None119 120def set_cache_dir(path: str) -> None:121    global _dnnlib_cache_dir122    _dnnlib_cache_dir = path123 124def make_cache_dir_path(*paths: str) -> str:125    if _dnnlib_cache_dir is not None:126        return os.path.join(_dnnlib_cache_dir, *paths)127    if 'DNNLIB_CACHE_DIR' in os.environ:128        return os.path.join(os.environ['DNNLIB_CACHE_DIR'], *paths)129    if 'HOME' in os.environ:130        return os.path.join(os.environ['HOME'], '.cache', 'dnnlib', *paths)131    if 'USERPROFILE' in os.environ:132        return os.path.join(os.environ['USERPROFILE'], '.cache', 'dnnlib', *paths)133    return os.path.join(tempfile.gettempdir(), '.cache', 'dnnlib', *paths)134 135# Small util functions136# ------------------------------------------------------------------------------------------137 138 139def format_time(seconds: Union[int, float]) -> str:140    """Convert the seconds to human readable string with days, hours, minutes and seconds."""141    s = int(np.rint(seconds))142 143    if s < 60:144        return "{0}s".format(s)145    elif s < 60 * 60:146        return "{0}m {1:02}s".format(s // 60, s % 60)147    elif s < 24 * 60 * 60:148        return "{0}h {1:02}m {2:02}s".format(s // (60 * 60), (s // 60) % 60, s % 60)149    else:150        return "{0}d {1:02}h {2:02}m".format(s // (24 * 60 * 60), (s // (60 * 60)) % 24, (s // 60) % 60)151 152 153def ask_yes_no(question: str) -> bool:154    """Ask the user the question until the user inputs a valid answer."""155    while True:156        try:157            print("{0} [y/n]".format(question))158            return strtobool(input().lower())159        except ValueError:160            pass161 162 163def tuple_product(t: Tuple) -> Any:164    """Calculate the product of the tuple elements."""165    result = 1166 167    for v in t:168        result *= v169 170    return result171 172 173_str_to_ctype = {174    "uint8": ctypes.c_ubyte,175    "uint16": ctypes.c_uint16,176    "uint32": ctypes.c_uint32,177    "uint64": ctypes.c_uint64,178    "int8": ctypes.c_byte,179    "int16": ctypes.c_int16,180    "int32": ctypes.c_int32,181    "int64": ctypes.c_int64,182    "float32": ctypes.c_float,183    "float64": ctypes.c_double184}185 186 187def get_dtype_and_ctype(type_obj: Any) -> Tuple[np.dtype, Any]:188    """Given a type name string (or an object having a __name__ attribute), return matching Numpy and ctypes types that have the same size in bytes."""189    type_str = None190 191    if isinstance(type_obj, str):192        type_str = type_obj193    elif hasattr(type_obj, "__name__"):194        type_str = type_obj.__name__195    elif hasattr(type_obj, "name"):196        type_str = type_obj.name197    else:198        raise RuntimeError("Cannot infer type name from input")199 200    assert type_str in _str_to_ctype.keys()201 202    my_dtype = np.dtype(type_str)203    my_ctype = _str_to_ctype[type_str]204 205    assert my_dtype.itemsize == ctypes.sizeof(my_ctype)206 207    return my_dtype, my_ctype208 209 210def is_pickleable(obj: Any) -> bool:211    try:212        with io.BytesIO() as stream:213            pickle.dump(obj, stream)214        return True215    except:216        return False217 218 219# Functionality to import modules/objects by name, and call functions by name220# ------------------------------------------------------------------------------------------221 222def get_module_from_obj_name(obj_name: str) -> Tuple[types.ModuleType, str]:223    """Searches for the underlying module behind the name to some python object.224    Returns the module and the object name (original name with module part removed)."""225 226    # allow convenience shorthands, substitute them by full names227    obj_name = re.sub("^np.", "numpy.", obj_name)228    obj_name = re.sub("^tf.", "tensorflow.", obj_name)229 230    # list alternatives for (module_name, local_obj_name)231    parts = obj_name.split(".")232    name_pairs = [(".".join(parts[:i]), ".".join(parts[i:])) for i in range(len(parts), 0, -1)]233 234    # try each alternative in turn235    for module_name, local_obj_name in name_pairs:236        try:237            module = importlib.import_module(module_name) # may raise ImportError238            get_obj_from_module(module, local_obj_name) # may raise AttributeError239            return module, local_obj_name240        except:241            pass242 243    # maybe some of the modules themselves contain errors?244    for module_name, _local_obj_name in name_pairs:245        try:246            importlib.import_module(module_name) # may raise ImportError247        except ImportError:248            if not str(sys.exc_info()[1]).startswith("No module named '" + module_name + "'"):249                raise250 251    # maybe the requested attribute is missing?252    for module_name, local_obj_name in name_pairs:253        try:254            module = importlib.import_module(module_name) # may raise ImportError255            get_obj_from_module(module, local_obj_name) # may raise AttributeError256        except ImportError:257            pass258 259    # we are out of luck, but we have no idea why260    raise ImportError(obj_name)261 262 263def get_obj_from_module(module: types.ModuleType, obj_name: str) -> Any:264    """Traverses the object name and returns the last (rightmost) python object."""265    if obj_name == '':266        return module267    obj = module268    for part in obj_name.split("."):269        obj = getattr(obj, part)270    return obj271 272 273def get_obj_by_name(name: str) -> Any:274    """Finds the python object with the given name."""275    module, obj_name = get_module_from_obj_name(name)276    return get_obj_from_module(module, obj_name)277 278 279def call_func_by_name(*args, func_name: str = None, **kwargs) -> Any:280    """Finds the python object with the given name and calls it as a function."""281    assert func_name is not None282    func_obj = get_obj_by_name(func_name)283    assert callable(func_obj)284    return func_obj(*args, **kwargs)285 286 287def construct_class_by_name(*args, class_name: str = None, **kwargs) -> Any:288    """Finds the python class with the given name and constructs it with the given arguments."""289    return call_func_by_name(*args, func_name=class_name, **kwargs)290 291 292def get_module_dir_by_obj_name(obj_name: str) -> str:293    """Get the directory path of the module containing the given object name."""294    module, _ = get_module_from_obj_name(obj_name)295    return os.path.dirname(inspect.getfile(module))296 297 298def is_top_level_function(obj: Any) -> bool:299    """Determine whether the given object is a top-level function, i.e., defined at module scope using 'def'."""300    return callable(obj) and obj.__name__ in sys.modules[obj.__module__].__dict__301 302 303def get_top_level_function_name(obj: Any) -> str:304    """Return the fully-qualified name of a top-level function."""305    assert is_top_level_function(obj)306    module = obj.__module__307    if module == '__main__':308        module = os.path.splitext(os.path.basename(sys.modules[module].__file__))[0]309    return module + "." + obj.__name__310 311 312# File system helpers313# ------------------------------------------------------------------------------------------314 315def list_dir_recursively_with_ignore(dir_path: str, ignores: List[str] = None, add_base_to_relative: bool = False) -> List[Tuple[str, str]]:316    """List all files recursively in a given directory while ignoring given file and directory names.317    Returns list of tuples containing both absolute and relative paths."""318    assert os.path.isdir(dir_path)319    base_name = os.path.basename(os.path.normpath(dir_path))320 321    if ignores is None:322        ignores = []323 324    result = []325 326    for root, dirs, files in os.walk(dir_path, topdown=True):327        for ignore_ in ignores:328            dirs_to_remove = [d for d in dirs if fnmatch.fnmatch(d, ignore_)]329 330            # dirs need to be edited in-place331            for d in dirs_to_remove:332                dirs.remove(d)333 334            files = [f for f in files if not fnmatch.fnmatch(f, ignore_)]335 336        absolute_paths = [os.path.join(root, f) for f in files]337        relative_paths = [os.path.relpath(p, dir_path) for p in absolute_paths]338 339        if add_base_to_relative:340            relative_paths = [os.path.join(base_name, p) for p in relative_paths]341 342        assert len(absolute_paths) == len(relative_paths)343        result += zip(absolute_paths, relative_paths)344 345    return result346 347 348def copy_files_and_create_dirs(files: List[Tuple[str, str]]) -> None:349    """Takes in a list of tuples of (src, dst) paths and copies files.350    Will create all necessary directories."""351    for file in files:352        target_dir_name = os.path.dirname(file[1])353 354        # will create all intermediate-level directories355        if not os.path.exists(target_dir_name):356            os.makedirs(target_dir_name)357 358        shutil.copyfile(file[0], file[1])359 360 361# URL helpers362# ------------------------------------------------------------------------------------------363 364def is_url(obj: Any, allow_file_urls: bool = False) -> bool:365    """Determine whether the given object is a valid URL string."""366    if not isinstance(obj, str) or not "://" in obj:367        return False368    if allow_file_urls and obj.startswith('file://'):369        return True370    try:371        res = requests.compat.urlparse(obj)372        if not res.scheme or not res.netloc or not "." in res.netloc:373            return False374        res = requests.compat.urlparse(requests.compat.urljoin(obj, "/"))375        if not res.scheme or not res.netloc or not "." in res.netloc:376            return False377    except:378        return False379    return True380 381 382def open_url(url: str, cache_dir: str = None, num_attempts: int = 10, verbose: bool = True, return_filename: bool = False, cache: bool = True) -> Any:383    """Download the given URL and return a binary-mode file object to access the data."""384    assert num_attempts >= 1385    assert not (return_filename and (not cache))386 387    # Doesn't look like an URL scheme so interpret it as a local filename.388    if not re.match('^[a-z]+://', url):389        return url if return_filename else open(url, "rb")390 391    # Handle file URLs.  This code handles unusual file:// patterns that392    # arise on Windows:393    #394    # file:///c:/foo.txt395    #396    # which would translate to a local '/c:/foo.txt' filename that's397    # invalid.  Drop the forward slash for such pathnames.398    #399    # If you touch this code path, you should test it on both Linux and400    # Windows.401    #402    # Some internet resources suggest using urllib.request.url2pathname() but403    # but that converts forward slashes to backslashes and this causes404    # its own set of problems.405    if url.startswith('file://'):406        filename = urllib.parse.urlparse(url).path407        if re.match(r'^/[a-zA-Z]:', filename):408            filename = filename[1:]409        return filename if return_filename else open(filename, "rb")410 411    assert is_url(url)412 413    # Lookup from cache.414    if cache_dir is None:415        cache_dir = make_cache_dir_path('downloads')416 417    url_md5 = hashlib.md5(url.encode("utf-8")).hexdigest()418    if cache:419        cache_files = glob.glob(os.path.join(cache_dir, url_md5 + "_*"))420        if len(cache_files) == 1:421            filename = cache_files[0]422            return filename if return_filename else open(filename, "rb")423 424    # Download.425    url_name = None426    url_data = None427    with requests.Session() as session:428        if verbose:429            print("Downloading %s ..." % url, end="", flush=True)430        for attempts_left in reversed(range(num_attempts)):431            try:432                with session.get(url) as res:433                    res.raise_for_status()434                    if len(res.content) == 0:435                        raise IOError("No data received")436 437                    if len(res.content) < 8192:438                        content_str = res.content.decode("utf-8")439                        if "download_warning" in res.headers.get("Set-Cookie", ""):440                            links = [html.unescape(link) for link in content_str.split('"') if "export=download" in link]441                            if len(links) == 1:442                                url = requests.compat.urljoin(url, links[0])443                                raise IOError("Google Drive virus checker nag")444                        if "Google Drive - Quota exceeded" in content_str:445                            raise IOError("Google Drive download quota exceeded -- please try again later")446 447                    match = re.search(r'filename="([^"]*)"', res.headers.get("Content-Disposition", ""))448                    url_name = match[1] if match else url449                    url_data = res.content450                    if verbose:451                        print(" done")452                    break453            except KeyboardInterrupt:454                raise455            except:456                if not attempts_left:457                    if verbose:458                        print(" failed")459                    raise460                if verbose:461                    print(".", end="", flush=True)462 463    # Save to cache.464    if cache:465        safe_name = re.sub(r"[^0-9a-zA-Z-._]", "_", url_name)466        cache_file = os.path.join(cache_dir, url_md5 + "_" + safe_name)467        temp_file = os.path.join(cache_dir, "tmp_" + uuid.uuid4().hex + "_" + url_md5 + "_" + safe_name)468        os.makedirs(cache_dir, exist_ok=True)469        with open(temp_file, "wb") as f:470            f.write(url_data)471        os.replace(temp_file, cache_file) # atomic472        if return_filename:473            return cache_file474 475    # Return data as file object.476    assert not return_filename477    return io.BytesIO(url_data)478