From 6292e72181326a82aeb7e4c4ea2291909843a46e Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 3 Feb 2022 12:37:42 +0100 Subject: [PATCH 1/5] Update plots for Chinese fonts --- utils/plots.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/utils/plots.py b/utils/plots.py index 74868403edc0..68d4fce567ae 100644 --- a/utils/plots.py +++ b/utils/plots.py @@ -89,10 +89,10 @@ def box_label(self, box, label='', color=(128, 128, 128), txt_color=(255, 255, 2 if label: w, h = self.font.getsize(label) # text width, height outside = box[1] - h >= 0 # label fits outside box - self.draw.rectangle([box[0], + self.draw.rectangle((box[0], box[1] - h if outside else box[1], box[0] + w + 1, - box[1] + 1 if outside else box[1] + h + 1], fill=color) + box[1] + 1 if outside else box[1] + h + 1), fill=color) # self.draw.text((box[0], box[1]), label, fill=txt_color, font=self.font, anchor='ls') # for PIL>8.0 self.draw.text((box[0], box[1] - h if outside else box[1]), label, fill=txt_color, font=self.font) else: # cv2 @@ -210,7 +210,7 @@ def plot_images(images, targets, paths=None, fname='images.jpg', names=None, max # Annotate fs = int((h + w) * ns * 0.01) # font size - annotator = Annotator(mosaic, line_width=round(fs / 10), font_size=fs, pil=True) + annotator = Annotator(mosaic, line_width=round(fs / 10), font_size=fs, pil=True, example=names) for i in range(i + 1): x, y = int(w * (i // ns)), int(h * (i % ns)) # block origin annotator.rectangle([x, y, x + w, y + h], None, (255, 255, 255), width=2) # borders From ded0cb52c74583b6e761695bb2fa4349b4eb4b25 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 3 Feb 2022 12:41:21 +0100 Subject: [PATCH 2/5] make is_chinese() non-str safe --- utils/general.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/general.py b/utils/general.py index 86e3b3c1c54b..2e70ce22c61e 100755 --- a/utils/general.py +++ b/utils/general.py @@ -207,7 +207,7 @@ def is_ascii(s=''): def is_chinese(s='人工智能'): # Is string composed of any Chinese characters? - return re.search('[\u4e00-\u9fff]', s) + return re.search('[\u4e00-\u9fff]', str(s)) def emojis(str=''): From ec2af1d916a61b354e0518c089bb214bd927a35c Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 3 Feb 2022 18:46:42 +0100 Subject: [PATCH 3/5] Add global FONT --- utils/general.py | 71 ++++++++++++++++++++++++++++-------------------- utils/plots.py | 17 +++++------- 2 files changed, 49 insertions(+), 39 deletions(-) diff --git a/utils/general.py b/utils/general.py index 2e70ce22c61e..66ede6386640 100755 --- a/utils/general.py +++ b/utils/general.py @@ -37,6 +37,7 @@ ROOT = FILE.parents[1] # YOLOv5 root directory NUM_THREADS = min(8, max(1, os.cpu_count() - 1)) # number of YOLOv5 multiprocessing threads VERBOSE = str(os.getenv('YOLOv5_VERBOSE', True)).lower() == 'true' # global verbose mode +FONT = 'Arial.ttf' # 'Arial.Unicode.ttf' for Chinese characters torch.set_printoptions(linewidth=320, precision=5, profile='long') np.set_printoptions(linewidth=320, formatter={'float_kind': '{:11.5g}'.format}) # format short g, %precision=5 @@ -55,6 +56,21 @@ def is_kaggle(): return False +def is_writeable(dir, test=False): + # Return True if directory has write permissions, test opening a file with write permissions if test=True + if test: # method 1 + file = Path(dir) / 'tmp.txt' + try: + with open(file, 'w'): # open file with write permissions + pass + file.unlink() # remove file + return True + except OSError: + return False + else: # method 2 + return os.access(dir, os.R_OK) # possible issues on Windows + + def set_logging(name=None, verbose=VERBOSE): # Sets level and returns logger if is_kaggle(): @@ -68,6 +84,22 @@ def set_logging(name=None, verbose=VERBOSE): LOGGER = set_logging('yolov5') # define globally (used in train.py, val.py, detect.py, etc.) +def user_config_dir(dir='Ultralytics', env_var='YOLOV5_CONFIG_DIR'): + # Return path of user configuration directory. Prefer environment variable if exists. Make dir if required. + env = os.getenv(env_var) + if env: + path = Path(env) # use environment variable + else: + cfg = {'Windows': 'AppData/Roaming', 'Linux': '.config', 'Darwin': 'Library/Application Support'} # 3 OS dirs + path = Path.home() / cfg.get(platform.system(), '') # OS-specific config dir + path = (path if is_writeable(path) else Path('/tmp')) / dir # GCP and AWS lambda fix, only /tmp is writeable + path.mkdir(exist_ok=True) # make if required + return path + + +CONFIG_DIR = user_config_dir() # Ultralytics settings dir + + class Profile(contextlib.ContextDecorator): # Usage: @Profile() decorator or 'with Profile():' context manager def __enter__(self): @@ -152,34 +184,6 @@ def get_latest_run(search_dir='.'): return max(last_list, key=os.path.getctime) if last_list else '' -def user_config_dir(dir='Ultralytics', env_var='YOLOV5_CONFIG_DIR'): - # Return path of user configuration directory. Prefer environment variable if exists. Make dir if required. - env = os.getenv(env_var) - if env: - path = Path(env) # use environment variable - else: - cfg = {'Windows': 'AppData/Roaming', 'Linux': '.config', 'Darwin': 'Library/Application Support'} # 3 OS dirs - path = Path.home() / cfg.get(platform.system(), '') # OS-specific config dir - path = (path if is_writeable(path) else Path('/tmp')) / dir # GCP and AWS lambda fix, only /tmp is writeable - path.mkdir(exist_ok=True) # make if required - return path - - -def is_writeable(dir, test=False): - # Return True if directory has write permissions, test opening a file with write permissions if test=True - if test: # method 1 - file = Path(dir) / 'tmp.txt' - try: - with open(file, 'w'): # open file with write permissions - pass - file.unlink() # remove file - return True - except OSError: - return False - else: # method 2 - return os.access(dir, os.R_OK) # possible issues on Windows - - def is_docker(): # Is environment a Docker container? return Path('/workspace').exists() # or Path('/.dockerenv').exists() @@ -207,7 +211,7 @@ def is_ascii(s=''): def is_chinese(s='人工智能'): # Is string composed of any Chinese characters? - return re.search('[\u4e00-\u9fff]', str(s)) + return True if re.search('[\u4e00-\u9fff]', str(s)) else False def emojis(str=''): @@ -378,6 +382,15 @@ def check_file(file, suffix=''): return files[0] # return file +def check_font(font=FONT): + # Download font to CONFIG_DIR if necessary + font = Path(font) + if not font.exists() and not (CONFIG_DIR / font.name).exists(): + url = "https://ultralytics.com/assets/" + font.name + LOGGER.info(f'Downloading {url} to {CONFIG_DIR / font.name}...') + torch.hub.download_url_to_file(url, str(font), progress=False) + + def check_dataset(data, autodownload=True): # Download and/or unzip dataset if not found locally # Usage: https://github.com/ultralytics/yolov5/releases/download/v1.0/coco128_with_yaml.zip diff --git a/utils/plots.py b/utils/plots.py index 68d4fce567ae..090a0bb66a6d 100644 --- a/utils/plots.py +++ b/utils/plots.py @@ -17,12 +17,11 @@ import torch from PIL import Image, ImageDraw, ImageFont -from utils.general import (LOGGER, Timeout, check_requirements, clip_coords, increment_path, is_ascii, is_chinese, - try_except, user_config_dir, xywh2xyxy, xyxy2xywh) +from utils.general import (LOGGER, CONFIG_DIR, FONT, Timeout, check_requirements, clip_coords, check_font, + increment_path, is_ascii, is_chinese, try_except, xywh2xyxy, xyxy2xywh) from utils.metrics import fitness # Settings -CONFIG_DIR = user_config_dir() # Ultralytics settings dir RANK = int(os.getenv('RANK', -1)) matplotlib.rc('font', **{'size': 11}) matplotlib.use('Agg') # for writing to files only @@ -49,16 +48,14 @@ def hex2rgb(h): # rgb order (PIL) colors = Colors() # create instance for 'from utils.plots import colors' -def check_font(font='Arial.ttf', size=10): +def check_pil_font(font=FONT, size=10): # Return a PIL TrueType Font, downloading to CONFIG_DIR if necessary font = Path(font) font = font if font.exists() else (CONFIG_DIR / font.name) try: return ImageFont.truetype(str(font) if font.exists() else font.name, size) except Exception as e: # download if missing - url = "https://ultralytics.com/assets/" + font.name - LOGGER.info(f'Downloading {url} to {font}...') - torch.hub.download_url_to_file(url, str(font), progress=False) + check_font(font) try: return ImageFont.truetype(str(font), size) except TypeError: @@ -67,7 +64,7 @@ def check_font(font='Arial.ttf', size=10): class Annotator: if RANK in (-1, 0): - check_font() # download TTF if necessary + check_pil_font() # download TTF if necessary # YOLOv5 Annotator for train/val mosaics and jpgs and detect/hub inference annotations def __init__(self, im, line_width=None, font_size=None, font='Arial.ttf', pil=False, example='abc'): @@ -76,8 +73,8 @@ def __init__(self, im, line_width=None, font_size=None, font='Arial.ttf', pil=Fa if self.pil: # use PIL self.im = im if isinstance(im, Image.Image) else Image.fromarray(im) self.draw = ImageDraw.Draw(self.im) - self.font = check_font(font='Arial.Unicode.ttf' if is_chinese(example) else font, - size=font_size or max(round(sum(self.im.size) / 2 * 0.035), 12)) + self.font = check_pil_font(font='Arial.Unicode.ttf' if is_chinese(example) else font, + size=font_size or max(round(sum(self.im.size) / 2 * 0.035), 12)) else: # use cv2 self.im = im self.lw = line_width or max(round(sum(im.shape) / 2 * 0.003), 2) # line width From d5128d836f491608db9027d71cfafa3d77180fef Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 3 Feb 2022 17:48:17 +0000 Subject: [PATCH 4/5] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- utils/plots.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/plots.py b/utils/plots.py index 090a0bb66a6d..be70ac8a030f 100644 --- a/utils/plots.py +++ b/utils/plots.py @@ -17,7 +17,7 @@ import torch from PIL import Image, ImageDraw, ImageFont -from utils.general import (LOGGER, CONFIG_DIR, FONT, Timeout, check_requirements, clip_coords, check_font, +from utils.general import (CONFIG_DIR, FONT, LOGGER, Timeout, check_font, check_requirements, clip_coords, increment_path, is_ascii, is_chinese, try_except, xywh2xyxy, xyxy2xywh) from utils.metrics import fitness From 15b93a5517e783b6ce105281ef459dae5b532541 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 3 Feb 2022 18:50:31 +0100 Subject: [PATCH 5/5] Update general.py --- utils/general.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/general.py b/utils/general.py index 66ede6386640..fce5e38c6c9e 100755 --- a/utils/general.py +++ b/utils/general.py @@ -37,7 +37,7 @@ ROOT = FILE.parents[1] # YOLOv5 root directory NUM_THREADS = min(8, max(1, os.cpu_count() - 1)) # number of YOLOv5 multiprocessing threads VERBOSE = str(os.getenv('YOLOv5_VERBOSE', True)).lower() == 'true' # global verbose mode -FONT = 'Arial.ttf' # 'Arial.Unicode.ttf' for Chinese characters +FONT = 'Arial.ttf' # https://ultralytics.com/assets/Arial.ttf torch.set_printoptions(linewidth=320, precision=5, profile='long') np.set_printoptions(linewidth=320, formatter={'float_kind': '{:11.5g}'.format}) # format short g, %precision=5