download.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340
  1. # Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved.
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. import os, sys
  15. import os.path as osp
  16. import hashlib
  17. import requests
  18. import shutil
  19. import tqdm
  20. import time
  21. import tarfile
  22. import zipfile
  23. from paddle.utils.download import _get_unique_endpoints
  24. PPDET_WEIGHTS_DOWNLOAD_URL_PREFIX = 'https://paddledet.bj.bcebos.com/'
  25. DOWNLOAD_RETRY_LIMIT = 3
  26. WEIGHTS_HOME = osp.expanduser("~/.cache/paddle/infer_weights")
  27. MODEL_URL_MD5_DICT = {
  28. 'https://bj.bcebos.com/v1/paddledet/models/pipeline/ch_PP-OCRv3_det_infer.tar.gz':
  29. '1b8eae0f098635699bd4e8bccf3067a7',
  30. 'https://bj.bcebos.com/v1/paddledet/models/pipeline/ch_PP-OCRv3_rec_infer.tar.gz':
  31. '64fa0e0701efd93c7db52a9b685b3de6',
  32. "https://bj.bcebos.com/v1/paddledet/models/pipeline/mot_ppyoloe_l_36e_ppvehicle.zip":
  33. "3859d1a26e0c498285c2374b1a347013",
  34. "https://bj.bcebos.com/v1/paddledet/models/pipeline/mot_ppyoloe_s_36e_ppvehicle.zip":
  35. "4ed58b546be2a76d8ccbb138f64874ac",
  36. "https://bj.bcebos.com/v1/paddledet/models/pipeline/dark_hrnet_w32_256x192.zip":
  37. "a20d5f6ca087bff0e9f2b18df45a36f2",
  38. "https://bj.bcebos.com/v1/paddledet/models/pipeline/PPLCNet_x1_0_person_attribute_945_infer.zip":
  39. "1dfb161bf12bbc1365b2ed6866674483",
  40. "https://videotag.bj.bcebos.com/PaddleVideo-release2.3/ppTSM_fight.zip":
  41. "5d4609142501258608bf0a1445eedaba",
  42. "https://bj.bcebos.com/v1/paddledet/models/pipeline/STGCN.zip":
  43. "cf1c3c4bae90b975accb954d13129ea4",
  44. "https://bj.bcebos.com/v1/paddledet/models/pipeline/ppyoloe_crn_s_80e_smoking_visdrone.zip":
  45. "4cd12ae55be8f0eb2b90c08ac3b48218",
  46. "https://bj.bcebos.com/v1/paddledet/models/pipeline/PPHGNet_tiny_calling_halfbody.zip":
  47. "cf86b87ace97540dace6ef08e62b584a",
  48. "https://bj.bcebos.com/v1/paddledet/models/pipeline/reid_model.zip":
  49. "fdc4dac38393b8e2b5921c1e1fdd5315"
  50. }
  51. def is_url(path):
  52. """
  53. Whether path is URL.
  54. Args:
  55. path (string): URL string or not.
  56. """
  57. return path.startswith('http://') \
  58. or path.startswith('https://') \
  59. or path.startswith('ppdet://')
  60. def parse_url(url):
  61. url = url.replace("ppdet://", PPDET_WEIGHTS_DOWNLOAD_URL_PREFIX)
  62. return url
  63. def map_path(url, root_dir, path_depth=1):
  64. # parse path after download to decompress under root_dir
  65. assert path_depth > 0, "path_depth should be a positive integer"
  66. dirname = url
  67. for _ in range(path_depth):
  68. dirname = osp.dirname(dirname)
  69. fpath = osp.relpath(url, dirname)
  70. zip_formats = ['.zip', '.tar', '.gz']
  71. for zip_format in zip_formats:
  72. fpath = fpath.replace(zip_format, '')
  73. return osp.join(root_dir, fpath)
  74. def _md5check(fullname, md5sum=None):
  75. if md5sum is None:
  76. return True
  77. md5 = hashlib.md5()
  78. with open(fullname, 'rb') as f:
  79. for chunk in iter(lambda: f.read(4096), b""):
  80. md5.update(chunk)
  81. calc_md5sum = md5.hexdigest()
  82. if calc_md5sum != md5sum:
  83. return False
  84. return True
  85. def _check_exist_file_md5(filename, md5sum, url):
  86. return _md5check(filename, md5sum)
  87. def _download(url, path, md5sum=None):
  88. """
  89. Download from url, save to path.
  90. url (str): download url
  91. path (str): download to given path
  92. """
  93. if not osp.exists(path):
  94. os.makedirs(path)
  95. fname = osp.split(url)[-1]
  96. fullname = osp.join(path, fname)
  97. retry_cnt = 0
  98. while not (osp.exists(fullname) and _check_exist_file_md5(fullname, md5sum,
  99. url)):
  100. if retry_cnt < DOWNLOAD_RETRY_LIMIT:
  101. retry_cnt += 1
  102. else:
  103. raise RuntimeError("Download from {} failed. "
  104. "Retry limit reached".format(url))
  105. # NOTE: windows path join may incur \, which is invalid in url
  106. if sys.platform == "win32":
  107. url = url.replace('\\', '/')
  108. req = requests.get(url, stream=True)
  109. if req.status_code != 200:
  110. raise RuntimeError("Downloading from {} failed with code "
  111. "{}!".format(url, req.status_code))
  112. # For protecting download interupted, download to
  113. # tmp_fullname firstly, move tmp_fullname to fullname
  114. # after download finished
  115. tmp_fullname = fullname + "_tmp"
  116. total_size = req.headers.get('content-length')
  117. with open(tmp_fullname, 'wb') as f:
  118. if total_size:
  119. for chunk in tqdm.tqdm(
  120. req.iter_content(chunk_size=1024),
  121. total=(int(total_size) + 1023) // 1024,
  122. unit='KB'):
  123. f.write(chunk)
  124. else:
  125. for chunk in req.iter_content(chunk_size=1024):
  126. if chunk:
  127. f.write(chunk)
  128. shutil.move(tmp_fullname, fullname)
  129. return fullname
  130. def _download_dist(url, path, md5sum=None):
  131. env = os.environ
  132. if 'PADDLE_TRAINERS_NUM' in env and 'PADDLE_TRAINER_ID' in env:
  133. trainer_id = int(env['PADDLE_TRAINER_ID'])
  134. num_trainers = int(env['PADDLE_TRAINERS_NUM'])
  135. if num_trainers <= 1:
  136. return _download(url, path, md5sum)
  137. else:
  138. fname = osp.split(url)[-1]
  139. fullname = osp.join(path, fname)
  140. lock_path = fullname + '.download.lock'
  141. if not osp.isdir(path):
  142. os.makedirs(path)
  143. if not osp.exists(fullname):
  144. from paddle.distributed import ParallelEnv
  145. unique_endpoints = _get_unique_endpoints(ParallelEnv()
  146. .trainer_endpoints[:])
  147. with open(lock_path, 'w'): # touch
  148. os.utime(lock_path, None)
  149. if ParallelEnv().current_endpoint in unique_endpoints:
  150. _download(url, path, md5sum)
  151. os.remove(lock_path)
  152. else:
  153. while os.path.exists(lock_path):
  154. time.sleep(0.5)
  155. return fullname
  156. else:
  157. return _download(url, path, md5sum)
  158. def _move_and_merge_tree(src, dst):
  159. """
  160. Move src directory to dst, if dst is already exists,
  161. merge src to dst
  162. """
  163. if not osp.exists(dst):
  164. shutil.move(src, dst)
  165. elif osp.isfile(src):
  166. shutil.move(src, dst)
  167. else:
  168. for fp in os.listdir(src):
  169. src_fp = osp.join(src, fp)
  170. dst_fp = osp.join(dst, fp)
  171. if osp.isdir(src_fp):
  172. if osp.isdir(dst_fp):
  173. _move_and_merge_tree(src_fp, dst_fp)
  174. else:
  175. shutil.move(src_fp, dst_fp)
  176. elif osp.isfile(src_fp) and \
  177. not osp.isfile(dst_fp):
  178. shutil.move(src_fp, dst_fp)
  179. def _decompress(fname):
  180. """
  181. Decompress for zip and tar file
  182. """
  183. # For protecting decompressing interupted,
  184. # decompress to fpath_tmp directory firstly, if decompress
  185. # successed, move decompress files to fpath and delete
  186. # fpath_tmp and remove download compress file.
  187. fpath = osp.split(fname)[0]
  188. fpath_tmp = osp.join(fpath, 'tmp')
  189. if osp.isdir(fpath_tmp):
  190. shutil.rmtree(fpath_tmp)
  191. os.makedirs(fpath_tmp)
  192. if fname.find('tar') >= 0:
  193. with tarfile.open(fname) as tf:
  194. tf.extractall(path=fpath_tmp)
  195. elif fname.find('zip') >= 0:
  196. with zipfile.ZipFile(fname) as zf:
  197. zf.extractall(path=fpath_tmp)
  198. elif fname.find('.txt') >= 0:
  199. return
  200. else:
  201. raise TypeError("Unsupport compress file type {}".format(fname))
  202. for f in os.listdir(fpath_tmp):
  203. src_dir = osp.join(fpath_tmp, f)
  204. dst_dir = osp.join(fpath, f)
  205. _move_and_merge_tree(src_dir, dst_dir)
  206. shutil.rmtree(fpath_tmp)
  207. os.remove(fname)
  208. def _decompress_dist(fname):
  209. env = os.environ
  210. if 'PADDLE_TRAINERS_NUM' in env and 'PADDLE_TRAINER_ID' in env:
  211. trainer_id = int(env['PADDLE_TRAINER_ID'])
  212. num_trainers = int(env['PADDLE_TRAINERS_NUM'])
  213. if num_trainers <= 1:
  214. _decompress(fname)
  215. else:
  216. lock_path = fname + '.decompress.lock'
  217. from paddle.distributed import ParallelEnv
  218. unique_endpoints = _get_unique_endpoints(ParallelEnv()
  219. .trainer_endpoints[:])
  220. # NOTE(dkp): _decompress_dist always performed after
  221. # _download_dist, in _download_dist sub-trainers is waiting
  222. # for download lock file release with sleeping, if decompress
  223. # prograss is very fast and finished with in the sleeping gap
  224. # time, e.g in tiny dataset such as coco_ce, spine_coco, main
  225. # trainer may finish decompress and release lock file, so we
  226. # only craete lock file in main trainer and all sub-trainer
  227. # wait 1s for main trainer to create lock file, for 1s is
  228. # twice as sleeping gap, this waiting time can keep all
  229. # trainer pipeline in order
  230. # **change this if you have more elegent methods**
  231. if ParallelEnv().current_endpoint in unique_endpoints:
  232. with open(lock_path, 'w'): # touch
  233. os.utime(lock_path, None)
  234. _decompress(fname)
  235. os.remove(lock_path)
  236. else:
  237. time.sleep(1)
  238. while os.path.exists(lock_path):
  239. time.sleep(0.5)
  240. else:
  241. _decompress(fname)
  242. def get_path(url, root_dir=WEIGHTS_HOME, md5sum=None, check_exist=True):
  243. """ Download from given url to root_dir.
  244. if file or directory specified by url is exists under
  245. root_dir, return the path directly, otherwise download
  246. from url and decompress it, return the path.
  247. url (str): download url
  248. root_dir (str): root dir for downloading
  249. md5sum (str): md5 sum of download package
  250. """
  251. # parse path after download to decompress under root_dir
  252. fullpath = map_path(url, root_dir)
  253. # For same zip file, decompressed directory name different
  254. # from zip file name, rename by following map
  255. decompress_name_map = {"ppTSM_fight": "ppTSM", }
  256. for k, v in decompress_name_map.items():
  257. if fullpath.find(k) >= 0:
  258. fullpath = osp.join(osp.split(fullpath)[0], v)
  259. if osp.exists(fullpath) and check_exist:
  260. if not osp.isfile(fullpath) or \
  261. _check_exist_file_md5(fullpath, md5sum, url):
  262. return fullpath, True
  263. else:
  264. os.remove(fullpath)
  265. fullname = _download_dist(url, root_dir, md5sum)
  266. # new weights format which postfix is 'pdparams' not
  267. # need to decompress
  268. if osp.splitext(fullname)[-1] not in ['.pdparams', '.yml']:
  269. _decompress_dist(fullname)
  270. return fullpath, False
  271. def get_weights_path(url):
  272. """Get weights path from WEIGHTS_HOME, if not exists,
  273. download it from url.
  274. """
  275. url = parse_url(url)
  276. md5sum = None
  277. if url in MODEL_URL_MD5_DICT.keys():
  278. md5sum = MODEL_URL_MD5_DICT[url]
  279. path, _ = get_path(url, WEIGHTS_HOME, md5sum)
  280. return path
  281. def auto_download_model(model_path):
  282. # auto download
  283. if is_url(model_path):
  284. weight = get_weights_path(model_path)
  285. return weight
  286. return None
  287. if __name__ == "__main__":
  288. model_path = "https://bj.bcebos.com/v1/paddledet/models/pipeline/mot_ppyoloe_l_36e_pipeline.zip"
  289. auto_download_model(model_path)