Repository: Project-MONAI/MONAI

Issue:
MMAR model description and downloading APIs (6/July)
Part of #2273 

Composing NGC MMAR URLs from MMAR item names, version number via API
- by default to fetch the latest?
- fetch the list of version numbers via API and download a specific version

(The advanced features that require NGC tool are not currently considered here, as we want to have a set of python APIs first)


Discussion:

Resolving patch:
diff --git a/monai/apps/mmars/mmars.py b/monai/apps/mmars/mmars.py
--- a/monai/apps/mmars/mmars.py
+++ b/monai/apps/mmars/mmars.py
@@ -46,7 +46,55 @@ def _get_model_spec(idx):
     raise ValueError(f"Unknown MODEL_DESC request: {idx}")
 
 
-def download_mmar(item, mmar_dir=None, progress: bool = True):
+def _get_all_ngc_models(pattern, page_index=0, page_size=50):
+    url = "https://api.ngc.nvidia.com/v2/search/catalog/resources/MODEL"
+    query_dict = {
+        "query": "",
+        "orderBy": [{"field": "score", "value": "DESC"}],
+        "queryFields": ["all", "description", "displayName", "name", "resourceId"],
+        "fields": [
+            "isPublic",
+            "attributes",
+            "guestAccess",
+            "name",
+            "orgName",
+            "teamName",
+            "displayName",
+            "dateModified",
+            "labels",
+            "description",
+        ],
+        "page": 0,
+    }
+
+    filter = [dict(field="name", value=f"*{pattern}*")]
+    query_dict["page"] = page_index
+    query_dict["pageSize"] = page_size
+    query_dict["filters"] = filter
+    query_str = json.dumps(query_dict)
+    full_url = f"{url}?q={query_str}"
+    requests_get, has_requests = optional_import("requests", name="get")
+    if has_requests:
+        resp = requests_get(full_url)
+    else:
+        raise ValueError("NGC API requires requests package.  Please install it.")
+    model_list = json.loads(resp.text)
+    model_dict = dict()
+    for result in model_list["results"]:
+        for model in result["resources"]:
+            current_res_id = model["resourceId"]
+            model_dict[current_res_id] = {"name": model["name"]}
+            for attribute in model["attributes"]:
+                if attribute["key"] == "latestVersionIdStr":
+                    model_dict[current_res_id]["latest"] = attribute["value"]
+    return model_dict
+
+
+def _get_ngc_url(model_name: str, version: str, model_prefix=""):
+    return f"https://api.ngc.nvidia.com/v2/models/{model_prefix}{model_name}/versions/{version}/zip"
+
+
+def download_mmar(item, mmar_dir=None, progress: bool = True, api: bool = False, version: int = 1):
     """
     Download and extract Medical Model Archive (MMAR) from Nvidia Clara Train.
 
@@ -57,18 +105,22 @@ def download_mmar(item, mmar_dir=None, progress: bool = True):
 
     Args:
         item: the corresponding model item from `MODEL_DESC`.
+          Or when api is True, the substring to query NGC's model name field.
         mmar_dir: target directory to store the MMAR, default is mmars subfolder under `torch.hub get_dir()`.
         progress: whether to display a progress bar.
+        api: whether to query NGC and download via api
+        version: which version of MMAR to download.  -1 means the latest from ngc.
 
     Examples::
         >>> from monai.apps import download_mmar
         >>> download_mmar("clara_pt_prostate_mri_segmentation_1", mmar_dir=".")
+        >>> download_mmar("prostate_mri_segmentation", mmar_dir=".", api=True)
+
 
     Returns:
         The local directory of the downloaded model.
+        If api is True, a list of local directories of downloaded models.
     """
-    if not isinstance(item, Mapping):
-        item = _get_model_spec(item)
     if not mmar_dir:
         get_dir, has_home = optional_import("torch.hub", name="get_dir")
         if has_home:
@@ -76,9 +128,34 @@ def download_mmar(item, mmar_dir=None, progress: bool = True):
         else:
             raise ValueError("mmar_dir=None, but no suitable default directory computed. Upgrade Pytorch to 1.6+ ?")
 
+    if api:
+        model_dict = _get_all_ngc_models(item)
+        if len(model_dict) == 0:
+            raise ValueError(f"api query returns no item for pattern {item}.  Please change or shorten it.")
+        model_dir_list = list()
+        for k, v in model_dict.items():
+            ver = v["latest"] if version == -1 else str(version)
+            download_url = _get_ngc_url(k, ver)
+            model_dir = os.path.join(mmar_dir, v["name"])
+            download_and_extract(
+                url=download_url,
+                filepath=os.path.join(mmar_dir, f'{v["name"]}_{ver}.zip'),
+                output_dir=model_dir,
+                hash_val=None,
+                hash_type="md5",
+                file_type="zip",
+                has_base=False,
+                progress=progress,
+            )
+            model_dir_list.append(model_dir)
+        return model_dir_list
+
+    if not isinstance(item, Mapping):
+        item = _get_model_spec(item)
+
     model_dir = os.path.join(mmar_dir, item[Keys.ID])
     download_and_extract(
-        url=item[Keys.URL],
+        url=_get_ngc_url(item[Keys.NAME], str(version), model_prefix="nvidia/med/"),
         filepath=os.path.join(mmar_dir, f"{item[Keys.ID]}.{item[Keys.FILE_TYPE]}"),
         output_dir=model_dir,
         hash_val=item[Keys.HASH_VAL],


Test patch: