From 300dff855b76a44480a6a07d89886fb30ebc7797 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sat, 23 Apr 2022 10:09:51 -0700 Subject: [PATCH 01/15] PyTorch Hub `_verbose=False` fix2 --- hubconf.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hubconf.py b/hubconf.py index 86aa07b9466f..f2e657d9f3a1 100644 --- a/hubconf.py +++ b/hubconf.py @@ -75,9 +75,9 @@ def yolov5n(pretrained=True, channels=3, classes=80, autoshape=True, verbose=Tru return _create('yolov5n', pretrained, channels, classes, autoshape, verbose, device) -def yolov5s(pretrained=True, channels=3, classes=80, autoshape=True, verbose=True, device=None): +def yolov5s(pretrained=True, channels=3, classes=80, autoshape=True, _verbose=True, device=None): # YOLOv5-small model https://github.com/ultralytics/yolov5 - return _create('yolov5s', pretrained, channels, classes, autoshape, verbose, device) + return _create('yolov5s', pretrained, channels, classes, autoshape, _verbose, device) def yolov5m(pretrained=True, channels=3, classes=80, autoshape=True, verbose=True, device=None): From 2223694485fe8be014bce6a61be11b5393259a3e Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sat, 23 Apr 2022 10:33:41 -0700 Subject: [PATCH 02/15] Update downloads.py --- utils/downloads.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/utils/downloads.py b/utils/downloads.py index 23433cb4549f..492ec304eb63 100644 --- a/utils/downloads.py +++ b/utils/downloads.py @@ -23,27 +23,30 @@ def gsutil_getsize(url=''): def safe_download(file, url, url2=None, min_bytes=1E0, error_msg=''): # Attempts to download file from url or url2, checks and removes incomplete downloads < min_bytes + from utils.general import LOGGER + file = Path(file) assert_msg = f"Downloaded file '{file}' does not exist or size is < min_bytes={min_bytes}" try: # url1 - print(f'Downloading {url} to {file}...') - torch.hub.download_url_to_file(url, str(file)) + LOGGER.info(f'Downloading {url} to {file}...') + torch.hub.download_url_to_file(url, str(file), progress=LOGGER.root.level <= 20) # 20=INFO logger level assert file.exists() and file.stat().st_size > min_bytes, assert_msg # check except Exception as e: # url2 file.unlink(missing_ok=True) # remove partial downloads - print(f'ERROR: {e}\nRe-attempting {url2 or url} to {file}...') + LOGGER.info(f'ERROR: {e}\nRe-attempting {url2 or url} to {file}...') os.system(f"curl -L '{url2 or url}' -o '{file}' --retry 3 -C -") # curl download, retry and resume on fail finally: if not file.exists() or file.stat().st_size < min_bytes: # check file.unlink(missing_ok=True) # remove partial downloads - print(f"ERROR: {assert_msg}\n{error_msg}") - print('') + LOGGER.info(f"ERROR: {assert_msg}\n{error_msg}") + LOGGER.info('') def attempt_download(file, repo='ultralytics/yolov5'): # from utils.downloads import *; attempt_download() # Attempt file download if does not exist - file = Path(str(file).strip().replace("'", '')) + from utils.general import LOGGER + file = Path(str(file).strip().replace("'", '')) if not file.exists(): # URL specified name = Path(urllib.parse.unquote(str(file))).name # decode '%2F' to '/' etc. @@ -51,7 +54,7 @@ def attempt_download(file, repo='ultralytics/yolov5'): # from utils.downloads i url = str(file).replace(':/', '://') # Pathlib turns :// -> :/ file = name.split('?')[0] # parse authentication https://url.com/file.txt?auth... if Path(file).is_file(): - print(f'Found {url} locally at {file}') # file already exists + LOGGER.info(f'Found {url} locally at {file}') # file already exists else: safe_download(file=file, url=url, min_bytes=1E5) return file @@ -125,7 +128,6 @@ def get_token(cookie="./cookie"): return line.split()[-1] return "" - # Google utils: https://cloud.google.com/storage/docs/reference/libraries ---------------------------------------------- # # From 259c50e6d71ea10b1bdfaf4152747388bd675e03 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 23 Apr 2022 17:33:57 +0000 Subject: [PATCH 03/15] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- utils/downloads.py | 1 + 1 file changed, 1 insertion(+) diff --git a/utils/downloads.py b/utils/downloads.py index 492ec304eb63..dc5f9229c277 100644 --- a/utils/downloads.py +++ b/utils/downloads.py @@ -128,6 +128,7 @@ def get_token(cookie="./cookie"): return line.split()[-1] return "" + # Google utils: https://cloud.google.com/storage/docs/reference/libraries ---------------------------------------------- # # From c30d49963194fa9ac305544d80e02aa228c53259 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sat, 23 Apr 2022 10:36:31 -0700 Subject: [PATCH 04/15] Update hubconf.py --- hubconf.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/hubconf.py b/hubconf.py index f2e657d9f3a1..4dfac4a43870 100644 --- a/hubconf.py +++ b/hubconf.py @@ -28,14 +28,15 @@ def _create(name, pretrained=True, channels=3, classes=80, autoshape=True, verbo """ from pathlib import Path + from utils.general import LOGGER, check_requirements, intersect_dicts, logging + if not verbose: + LOGGER.setLevel(logging.WARNING) + from models.common import AutoShape, DetectMultiBackend from models.yolo import Model from utils.downloads import attempt_download - from utils.general import LOGGER, check_requirements, intersect_dicts, logging from utils.torch_utils import select_device - if not verbose: - LOGGER.setLevel(logging.WARNING) check_requirements(exclude=('tensorboard', 'thop', 'opencv-python')) name = Path(name) path = name.with_suffix('.pt') if name.suffix == '' else name # checkpoint path From 241af9240367a3adc98bc19dbe8d4a557821826a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 23 Apr 2022 17:36:47 +0000 Subject: [PATCH 05/15] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- hubconf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hubconf.py b/hubconf.py index 4dfac4a43870..1424193ff705 100644 --- a/hubconf.py +++ b/hubconf.py @@ -31,7 +31,7 @@ def _create(name, pretrained=True, channels=3, classes=80, autoshape=True, verbo from utils.general import LOGGER, check_requirements, intersect_dicts, logging if not verbose: LOGGER.setLevel(logging.WARNING) - + from models.common import AutoShape, DetectMultiBackend from models.yolo import Model from utils.downloads import attempt_download From 00b0242a53d8a2ea87f162663e4bb2cc994fb692 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sat, 23 Apr 2022 10:42:21 -0700 Subject: [PATCH 06/15] Update --- utils/downloads.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/utils/downloads.py b/utils/downloads.py index dc5f9229c277..69106dbdcd40 100644 --- a/utils/downloads.py +++ b/utils/downloads.py @@ -8,6 +8,7 @@ import subprocess import time import urllib +import logging from pathlib import Path from zipfile import ZipFile @@ -29,7 +30,7 @@ def safe_download(file, url, url2=None, min_bytes=1E0, error_msg=''): assert_msg = f"Downloaded file '{file}' does not exist or size is < min_bytes={min_bytes}" try: # url1 LOGGER.info(f'Downloading {url} to {file}...') - torch.hub.download_url_to_file(url, str(file), progress=LOGGER.root.level <= 20) # 20=INFO logger level + torch.hub.download_url_to_file(url, str(file), progress=LOGGER.level <= logging.INFO) assert file.exists() and file.stat().st_size > min_bytes, assert_msg # check except Exception as e: # url2 file.unlink(missing_ok=True) # remove partial downloads @@ -128,7 +129,6 @@ def get_token(cookie="./cookie"): return line.split()[-1] return "" - # Google utils: https://cloud.google.com/storage/docs/reference/libraries ---------------------------------------------- # # From caeaa937c26ff0c8b6ecec20a0b666d86bfa07a2 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 23 Apr 2022 17:42:40 +0000 Subject: [PATCH 07/15] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- utils/downloads.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/utils/downloads.py b/utils/downloads.py index 69106dbdcd40..776a8bba1755 100644 --- a/utils/downloads.py +++ b/utils/downloads.py @@ -3,12 +3,12 @@ Download utils """ +import logging import os import platform import subprocess import time import urllib -import logging from pathlib import Path from zipfile import ZipFile @@ -129,6 +129,7 @@ def get_token(cookie="./cookie"): return line.split()[-1] return "" + # Google utils: https://cloud.google.com/storage/docs/reference/libraries ---------------------------------------------- # # From 6c176aa148a849fa6fb4d414d2e2c9669be86cba Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sat, 23 Apr 2022 10:44:50 -0700 Subject: [PATCH 08/15] Update --- hubconf.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/hubconf.py b/hubconf.py index 1424193ff705..ab4214a44f99 100644 --- a/hubconf.py +++ b/hubconf.py @@ -28,15 +28,15 @@ def _create(name, pretrained=True, channels=3, classes=80, autoshape=True, verbo """ from pathlib import Path - from utils.general import LOGGER, check_requirements, intersect_dicts, logging - if not verbose: - LOGGER.setLevel(logging.WARNING) - from models.common import AutoShape, DetectMultiBackend from models.yolo import Model from utils.downloads import attempt_download + from utils.general import LOGGER, check_requirements, intersect_dicts, logging from utils.torch_utils import select_device + if not verbose: + LOGGER.setLevel(logging.WARNING) + check_requirements(exclude=('tensorboard', 'thop', 'opencv-python')) name = Path(name) path = name.with_suffix('.pt') if name.suffix == '' else name # checkpoint path @@ -122,7 +122,8 @@ def yolov5x6(pretrained=True, channels=3, classes=80, autoshape=True, verbose=Tr if __name__ == '__main__': - model = _create(name='yolov5s', pretrained=True, channels=3, classes=80, autoshape=True, verbose=True) # pretrained + model = _create(name='yolov5s', pretrained=True, channels=3, classes=80, autoshape=True, + verbose=False) # pretrained # model = custom(path='path/to/model.pt') # custom # Verify inference From 5e4e28605b5c4b97696160e3eb811257738495e3 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sat, 23 Apr 2022 10:50:08 -0700 Subject: [PATCH 09/15] Update --- hubconf.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/hubconf.py b/hubconf.py index ab4214a44f99..fc1c80f89863 100644 --- a/hubconf.py +++ b/hubconf.py @@ -122,8 +122,7 @@ def yolov5x6(pretrained=True, channels=3, classes=80, autoshape=True, verbose=Tr if __name__ == '__main__': - model = _create(name='yolov5s', pretrained=True, channels=3, classes=80, autoshape=True, - verbose=False) # pretrained + model = _create(name='yolov5s', pretrained=True, channels=3, classes=80, autoshape=True, verbose=True) # model = custom(path='path/to/model.pt') # custom # Verify inference From 24b0235a7313586881397ca893868e9cfd850eda Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sat, 23 Apr 2022 10:53:56 -0700 Subject: [PATCH 10/15] Update --- utils/general.py | 1 + 1 file changed, 1 insertion(+) diff --git a/utils/general.py b/utils/general.py index 31abd9420134..9aa500106060 100755 --- a/utils/general.py +++ b/utils/general.py @@ -431,6 +431,7 @@ def check_font(font=FONT, progress=False): file = CONFIG_DIR / font.name if not font.exists() and not file.exists(): url = "https://ultralytics.com/assets/" + font.name + print(LOGGER.level) LOGGER.info(f'Downloading {url} to {file}...') torch.hub.download_url_to_file(url, str(file), progress=progress) From 8dfcd5510c54ddbd3823f340e1e0269dd73db610 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sat, 23 Apr 2022 10:57:51 -0700 Subject: [PATCH 11/15] Update --- utils/plots.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/utils/plots.py b/utils/plots.py index 842894e745df..c0d196188751 100644 --- a/utils/plots.py +++ b/utils/plots.py @@ -66,9 +66,6 @@ def check_pil_font(font=FONT, size=10): class Annotator: - if RANK in (-1, 0): - 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'): assert im.data.contiguous, 'Image not contiguous. Apply np.ascontiguousarray(im) to Annotator() input images.' From c21f134a4a0a5984939871324f31de1fe9477fb5 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sat, 23 Apr 2022 11:12:06 -0700 Subject: [PATCH 12/15] Update --- train.py | 4 +--- utils/general.py | 1 + 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/train.py b/train.py index 876e1097e8e8..50501fc94091 100644 --- a/train.py +++ b/train.py @@ -54,7 +54,7 @@ from utils.loggers.wandb.wandb_utils import check_wandb_resume from utils.loss import ComputeLoss from utils.metrics import fitness -from utils.plots import check_font, plot_evolve, plot_labels +from utils.plots import plot_evolve, plot_labels from utils.torch_utils import EarlyStopping, ModelEMA, de_parallel, select_device, torch_distributed_zero_first LOCAL_RANK = int(os.getenv('LOCAL_RANK', -1)) # https://pytorch.org/docs/stable/elastic/run.html @@ -105,8 +105,6 @@ def train(hyp, opt, device, callbacks): # hyp is path/to/hyp.yaml or hyp dictio init_seeds(1 + RANK) with torch_distributed_zero_first(LOCAL_RANK): data_dict = data_dict or check_dataset(data) # check if None - if not is_ascii(data_dict['names']): # non-latin labels, i.e. asian, arabic, cyrillic - check_font('Arial.Unicode.ttf', progress=True) train_path, val_path = data_dict['train'], data_dict['val'] nc = 1 if single_cls else int(data_dict['nc']) # number of classes names = ['item'] if single_cls and len(data_dict['names']) != 1 else data_dict['names'] # class names diff --git a/utils/general.py b/utils/general.py index 9aa500106060..7c45e928938d 100755 --- a/utils/general.py +++ b/utils/general.py @@ -464,6 +464,7 @@ def check_dataset(data, autodownload=True): assert 'nc' in data, "Dataset 'nc' key missing." if 'names' not in data: data['names'] = [f'class{i}' for i in range(data['nc'])] # assign class names if missing + check_font('Arial.Unicode.ttf' if is_ascii(data['names']) else 'Arial.ttf', progress=True) # download fonts train, val, test, s = (data.get(x) for x in ('train', 'val', 'test', 'download')) if val: val = [Path(x).resolve() for x in (val if isinstance(val, list) else [val])] # val path From 9712f2c4bdaa087c93d00a919cd74af36c9dcb65 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sat, 23 Apr 2022 11:14:03 -0700 Subject: [PATCH 13/15] Update --- utils/general.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/utils/general.py b/utils/general.py index 7c45e928938d..f2a5af250bb7 100755 --- a/utils/general.py +++ b/utils/general.py @@ -431,7 +431,6 @@ def check_font(font=FONT, progress=False): file = CONFIG_DIR / font.name if not font.exists() and not file.exists(): url = "https://ultralytics.com/assets/" + font.name - print(LOGGER.level) LOGGER.info(f'Downloading {url} to {file}...') torch.hub.download_url_to_file(url, str(file), progress=progress) @@ -464,7 +463,7 @@ def check_dataset(data, autodownload=True): assert 'nc' in data, "Dataset 'nc' key missing." if 'names' not in data: data['names'] = [f'class{i}' for i in range(data['nc'])] # assign class names if missing - check_font('Arial.Unicode.ttf' if is_ascii(data['names']) else 'Arial.ttf', progress=True) # download fonts + check_font('Arial.ttf' if is_ascii(data['names']) else 'Arial.Unicode.ttf', progress=True) # download fonts train, val, test, s = (data.get(x) for x in ('train', 'val', 'test', 'download')) if val: val = [Path(x).resolve() for x in (val if isinstance(val, list) else [val])] # val path From 52907c0dea2d1ea4027435f226e0d176ff931ea2 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sat, 23 Apr 2022 11:17:46 -0700 Subject: [PATCH 14/15] Update --- hubconf.py | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/hubconf.py b/hubconf.py index fc1c80f89863..4e05149026b3 100644 --- a/hubconf.py +++ b/hubconf.py @@ -66,14 +66,14 @@ def _create(name, pretrained=True, channels=3, classes=80, autoshape=True, verbo raise Exception(s) from e -def custom(path='path/to/model.pt', autoshape=True, verbose=True, device=None): +def custom(path='path/to/model.pt', autoshape=True, _verbose=True, device=None): # YOLOv5 custom or local model - return _create(path, autoshape=autoshape, verbose=verbose, device=device) + return _create(path, autoshape=autoshape, verbose=_verbose, device=device) -def yolov5n(pretrained=True, channels=3, classes=80, autoshape=True, verbose=True, device=None): +def yolov5n(pretrained=True, channels=3, classes=80, autoshape=True, _verbose=True, device=None): # YOLOv5-nano model https://github.com/ultralytics/yolov5 - return _create('yolov5n', pretrained, channels, classes, autoshape, verbose, device) + return _create('yolov5n', pretrained, channels, classes, autoshape, _verbose, device) def yolov5s(pretrained=True, channels=3, classes=80, autoshape=True, _verbose=True, device=None): @@ -81,44 +81,44 @@ def yolov5s(pretrained=True, channels=3, classes=80, autoshape=True, _verbose=Tr return _create('yolov5s', pretrained, channels, classes, autoshape, _verbose, device) -def yolov5m(pretrained=True, channels=3, classes=80, autoshape=True, verbose=True, device=None): +def yolov5m(pretrained=True, channels=3, classes=80, autoshape=True, _verbose=True, device=None): # YOLOv5-medium model https://github.com/ultralytics/yolov5 - return _create('yolov5m', pretrained, channels, classes, autoshape, verbose, device) + return _create('yolov5m', pretrained, channels, classes, autoshape, _verbose, device) -def yolov5l(pretrained=True, channels=3, classes=80, autoshape=True, verbose=True, device=None): +def yolov5l(pretrained=True, channels=3, classes=80, autoshape=True, _verbose=True, device=None): # YOLOv5-large model https://github.com/ultralytics/yolov5 - return _create('yolov5l', pretrained, channels, classes, autoshape, verbose, device) + return _create('yolov5l', pretrained, channels, classes, autoshape, _verbose, device) -def yolov5x(pretrained=True, channels=3, classes=80, autoshape=True, verbose=True, device=None): +def yolov5x(pretrained=True, channels=3, classes=80, autoshape=True, _verbose=True, device=None): # YOLOv5-xlarge model https://github.com/ultralytics/yolov5 - return _create('yolov5x', pretrained, channels, classes, autoshape, verbose, device) + return _create('yolov5x', pretrained, channels, classes, autoshape, _verbose, device) -def yolov5n6(pretrained=True, channels=3, classes=80, autoshape=True, verbose=True, device=None): +def yolov5n6(pretrained=True, channels=3, classes=80, autoshape=True, _verbose=True, device=None): # YOLOv5-nano-P6 model https://github.com/ultralytics/yolov5 - return _create('yolov5n6', pretrained, channels, classes, autoshape, verbose, device) + return _create('yolov5n6', pretrained, channels, classes, autoshape, _verbose, device) -def yolov5s6(pretrained=True, channels=3, classes=80, autoshape=True, verbose=True, device=None): +def yolov5s6(pretrained=True, channels=3, classes=80, autoshape=True, _verbose=True, device=None): # YOLOv5-small-P6 model https://github.com/ultralytics/yolov5 - return _create('yolov5s6', pretrained, channels, classes, autoshape, verbose, device) + return _create('yolov5s6', pretrained, channels, classes, autoshape, _verbose, device) -def yolov5m6(pretrained=True, channels=3, classes=80, autoshape=True, verbose=True, device=None): +def yolov5m6(pretrained=True, channels=3, classes=80, autoshape=True, _verbose=True, device=None): # YOLOv5-medium-P6 model https://github.com/ultralytics/yolov5 - return _create('yolov5m6', pretrained, channels, classes, autoshape, verbose, device) + return _create('yolov5m6', pretrained, channels, classes, autoshape, _verbose, device) -def yolov5l6(pretrained=True, channels=3, classes=80, autoshape=True, verbose=True, device=None): +def yolov5l6(pretrained=True, channels=3, classes=80, autoshape=True, _verbose=True, device=None): # YOLOv5-large-P6 model https://github.com/ultralytics/yolov5 - return _create('yolov5l6', pretrained, channels, classes, autoshape, verbose, device) + return _create('yolov5l6', pretrained, channels, classes, autoshape, _verbose, device) -def yolov5x6(pretrained=True, channels=3, classes=80, autoshape=True, verbose=True, device=None): +def yolov5x6(pretrained=True, channels=3, classes=80, autoshape=True, _verbose=True, device=None): # YOLOv5-xlarge-P6 model https://github.com/ultralytics/yolov5 - return _create('yolov5x6', pretrained, channels, classes, autoshape, verbose, device) + return _create('yolov5x6', pretrained, channels, classes, autoshape, _verbose, device) if __name__ == '__main__': From b7f49fe1a59ff816b3d6af78a7bb6d3621d9e5f0 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sat, 23 Apr 2022 11:20:31 -0700 Subject: [PATCH 15/15] Update --- utils/general.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/general.py b/utils/general.py index f2a5af250bb7..67f01b54ab88 100755 --- a/utils/general.py +++ b/utils/general.py @@ -463,7 +463,6 @@ def check_dataset(data, autodownload=True): assert 'nc' in data, "Dataset 'nc' key missing." if 'names' not in data: data['names'] = [f'class{i}' for i in range(data['nc'])] # assign class names if missing - check_font('Arial.ttf' if is_ascii(data['names']) else 'Arial.Unicode.ttf', progress=True) # download fonts train, val, test, s = (data.get(x) for x in ('train', 'val', 'test', 'download')) if val: val = [Path(x).resolve() for x in (val if isinstance(val, list) else [val])] # val path @@ -491,6 +490,7 @@ def check_dataset(data, autodownload=True): else: raise Exception(emojis('Dataset not found ❌')) + check_font('Arial.ttf' if is_ascii(data['names']) else 'Arial.Unicode.ttf', progress=True) # download fonts return data # dictionary