Repository: Project-MONAI/MONAI

Issue:
progress bar with tqdm
**Is your feature request related to a problem? Please describe.**
instead of implementing the progress bar related functionality, [tqdm](https://github.com/tqdm/tqdm) looks like a good library to use directly. this ticket requests replacing the existing code to use tqdm implementations 



Discussion:
this feature is postponed, please see https://github.com/Project-MONAI/MONAI/pull/633
(lacks python 3.8 support in multi-processing)

Resolving patch:
diff --git a/monai/apps/utils.py b/monai/apps/utils.py
--- a/monai/apps/utils.py
+++ b/monai/apps/utils.py
@@ -14,15 +14,23 @@
 import os
 import shutil
 import tarfile
+import warnings
 import zipfile
-from typing import Optional
+from typing import TYPE_CHECKING, Optional
 from urllib.error import ContentTooShortError, HTTPError, URLError
 from urllib.request import Request, urlopen, urlretrieve
 
-from monai.utils import optional_import, progress_bar
+from monai.utils import min_version, optional_import
 
 gdown, has_gdown = optional_import("gdown", "3.6")
 
+if TYPE_CHECKING:
+    from tqdm import tqdm
+
+    has_tqdm = True
+else:
+    tqdm, has_tqdm = optional_import("tqdm", "4.47.0", min_version, "tqdm")
+
 
 def check_hash(filepath: str, val: Optional[str] = None, hash_type: str = "md5") -> bool:
     """
@@ -105,7 +113,17 @@ def download_url(url: str, filepath: str, hash_val: Optional[str] = None, hash_t
 
         try:
             file_size = int(urlopen(url).info().get("Content-Length", -1))
-            progress_bar(index=first_byte, count=file_size)
+            if has_tqdm:
+                pbar = tqdm(
+                    unit="B",
+                    unit_scale=True,
+                    unit_divisor=1024,
+                    miniters=1,
+                    desc=filepath.split(os.sep)[-1],
+                    total=file_size,
+                )
+            else:
+                warnings.warn("tqdm is not installed, will not show the downloading progress bar.")
 
             while first_byte < file_size:
                 last_byte = first_byte + block_size if first_byte + block_size < file_size else file_size - 1
@@ -115,8 +133,11 @@ def download_url(url: str, filepath: str, hash_val: Optional[str] = None, hash_t
                 data_chunk = urlopen(req, timeout=10).read()
                 with open(tmp_file_path, "ab") as f:
                     f.write(data_chunk)
-                progress_bar(index=last_byte, count=file_size)
+                if has_tqdm:
+                    pbar.update(len(data_chunk))
                 first_byte = last_byte + 1
+            if has_tqdm:
+                pbar.close()
         except IOError as e:
             logging.debug("IO Error - %s" % e)
         finally:
@@ -129,11 +150,37 @@ def download_url(url: str, filepath: str, hash_val: Optional[str] = None, hash_t
     else:
         os.makedirs(os.path.dirname(filepath), exist_ok=True)
 
-        def _process_hook(blocknum: int, blocksize: int, totalsize: int):
-            progress_bar(blocknum * blocksize, totalsize, f"Downloading {filepath.split('/')[-1]}:")
+        class TqdmUpTo(tqdm):
+            """
+            Provides `update_to(n)` which uses `tqdm.update(delta_n)`.
+            Inspired by the example in https://github.com/tqdm/tqdm.
+
+            """
+
+            def update_to(self, b: int = 1, bsize: int = 1, tsize: Optional[int] = None):
+                """
+                b: number of blocks transferred so far, default: 1.
+                bsize: size of each block (in tqdm units), default: 1.
+                tsize: total size (in tqdm units). if None, remains unchanged.
+
+                """
+                if tsize is not None:
+                    self.total = tsize
+                self.update(b * bsize - self.n)  # will also set self.n = b * bsize
 
         try:
-            urlretrieve(url, filepath, reporthook=_process_hook)
+            if has_tqdm:
+                with TqdmUpTo(
+                    unit="B",
+                    unit_scale=True,
+                    unit_divisor=1024,
+                    miniters=1,
+                    desc=filepath.split(os.sep)[-1],
+                ) as t:
+                    urlretrieve(url, filepath, reporthook=t.update_to)
+            else:
+                warnings.warn("tqdm is not installed, will not show the downloading progress bar.")
+                urlretrieve(url, filepath)
             print(f"\ndownloaded file: {filepath}.")
         except (URLError, HTTPError, ContentTooShortError, IOError) as e:
             print(f"download failed from {url} to {filepath}.")
diff --git a/monai/config/deviceconfig.py b/monai/config/deviceconfig.py
--- a/monai/config/deviceconfig.py
+++ b/monai/config/deviceconfig.py
@@ -82,6 +82,14 @@
 except (ImportError, AttributeError):
     itk_version = "NOT INSTALLED or UNKNOWN VERSION."
 
+try:
+    import tqdm
+
+    tqdm_version = tqdm.__version__
+    del tqdm
+except (ImportError, AttributeError):
+    tqdm_version = "NOT INSTALLED or UNKNOWN VERSION."
+
 
 def get_config_values():
     """
@@ -111,6 +119,7 @@ def get_optional_config_values():
     output["gdown"] = gdown_version
     output["TorchVision"] = torchvision_version
     output["ITK"] = itk_version
+    output["tqdm"] = tqdm_version
 
     return output
 
diff --git a/monai/data/dataset.py b/monai/data/dataset.py
--- a/monai/data/dataset.py
+++ b/monai/data/dataset.py
@@ -15,16 +15,24 @@
 import sys
 import threading
 import time
+import warnings
 from multiprocessing.pool import ThreadPool
 from pathlib import Path
-from typing import Any, Callable, List, Optional, Sequence, Tuple, Union
+from typing import TYPE_CHECKING, Any, Callable, List, Optional, Sequence, Union
 
 import numpy as np
 import torch
 from torch.utils.data import Dataset as _TorchDataset
 
 from monai.transforms import Compose, Randomizable, Transform, apply_transform
-from monai.utils import get_seed, progress_bar
+from monai.utils import get_seed, min_version, optional_import
+
+if TYPE_CHECKING:
+    from tqdm import tqdm
+
+    has_tqdm = True
+else:
+    tqdm, has_tqdm = optional_import("tqdm", "4.47.0", min_version, "tqdm")
 
 
 class Dataset(_TorchDataset):
@@ -271,18 +279,27 @@ def __init__(
         self.cache_num = min(cache_num, int(len(data) * cache_rate), len(data))
         if self.cache_num > 0:
             self._cache = [None] * self.cache_num
+            if has_tqdm:
+                pbar = tqdm(total=self.cache_num, desc="Load and cache transformed data")
+            else:
+                warnings.warn("tqdm is not installed, will not show the caching progress bar.")
+                pbar = None
+
             if num_workers > 0:
                 self._item_processed = 0
                 self._thread_lock = threading.Lock()
                 with ThreadPool(num_workers) as p:
                     p.map(
                         self._load_cache_item_thread,
-                        [(i, data[i], transform.transforms) for i in range(self.cache_num)],
+                        [(i, data[i], transform.transforms, pbar) for i in range(self.cache_num)],
                     )
             else:
                 for i in range(self.cache_num):
                     self._cache[i] = self._load_cache_item(data[i], transform.transforms)
-                    progress_bar(i + 1, self.cache_num, "Load and cache transformed data: ")
+                    if pbar is not None:
+                        pbar.update(1)
+            if pbar is not None:
+                pbar.close()
 
     def _load_cache_item(self, item: Any, transforms: Sequence[Callable]):
         """
@@ -297,19 +314,20 @@ def _load_cache_item(self, item: Any, transforms: Sequence[Callable]):
             item = apply_transform(_transform, item)
         return item
 
-    def _load_cache_item_thread(self, args: Tuple[int, Any, Sequence[Callable]]) -> None:
+    def _load_cache_item_thread(self, args: Any) -> None:
         """
         Args:
-            args: tuple with contents (i, item, transforms).
+            args: tuple with contents (i, item, transforms, pbar).
                 i: the index to load the cached item to.
                 item: input item to load and transform to generate dataset for model.
                 transforms: transforms to execute operations on input item.
+                pbar: tqdm progress bar
         """
-        i, item, transforms = args
+        i, item, transforms, pbar = args
         self._cache[i] = self._load_cache_item(item, transforms)
-        with self._thread_lock:
-            self._item_processed += 1
-            progress_bar(self._item_processed, self.cache_num, "Load and cache transformed data: ")
+        if pbar is not None:
+            with self._thread_lock:
+                pbar.update(1)
 
     def __getitem__(self, index):
         if index < self.cache_num:


Test patch: