Spaces:
Running
Running
File size: 21,360 Bytes
d5d20be |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 |
# -*- coding: utf-8 -*-
"""
@Time : 2022/8/27 14:17
@Author : cuny
@File : app.py
@Software : PyCharm
@Introduce:
查看包版本等一系列操作
"""
import os
import sys
import json
import shutil
import zipfile
import requests
from argparse import ArgumentParser
from importlib.metadata import version
try: # 加上这个try的原因在于本地环境和云函数端的import形式有所不同
from qcloud_cos import CosConfig
from qcloud_cos import CosS3Client
except ImportError:
try:
from qcloud_cos_v5 import CosConfig
from qcloud_cos_v5 import CosS3Client
from qcloud_cos.cos_exception import CosServiceError
except ImportError:
raise ImportError("请下载腾讯云COS相关代码包:pip install cos-python-sdk-v5")
class HivisionaiParams(object):
"""
定义一些基本常量
"""
# 文件所在路径
# 包名称
package_name = "HY-sdk"
# 腾讯云相关变量
region = "ap-beijing"
zip_key = "HY-sdk/" # zip存储的云端文件夹路径,这里改了publish.yml也需要更改
# 云端用户配置,如果在cloud_config_save不存在,就需要下载此文件
user_url = "https://hy-sdk-config-1305323352.cos.ap-beijing.myqcloud.com/sdk-user/user_config.json"
bucket = "cloud-public-static-1306602019"
# 压缩包类型
file_format = ".zip"
# 下载路径(.hivisionai文件夹路径)
download_path = os.path.expandvars('$HOME')
# zip文件、zip解压缩文件的存放路径
save_folder = f"{os.path.expandvars('$HOME')}/.hivisionai/sdk"
# 腾讯云配置文件存放路径
cloud_config_save = f"{os.path.expandvars('$HOME')}/.hivisionai/user_config.json"
# 项目路径
hivisionai_path = os.path.dirname(os.path.dirname(__file__))
# 使用hivisionai的路径
getcwd = os.getcwd()
# HY-func的依赖配置
# 每个依赖会包含三个参数,保存路径(save_path,相对于HY_func的路径)、下载url(url)
functionDependence = {
"configs": [
# --------- 配置文件部分
# _lib
{
"url": "https://hy-sdk-config-1305323352.cos.ap-beijing.myqcloud.com/hy-func/_lib/config/aliyun-human-matting-api.json",
"save_path": "_lib/config/aliyun-human-matting-api.json"
},
{
"url": "https://hy-sdk-config-1305323352.cos.ap-beijing.myqcloud.com/hy-func/_lib/config/megvii-face-plus-api.json",
"save_path": "_lib/config/megvii-face-plus-api.json"
},
{
"url": "https://hy-sdk-config-1305323352.cos.ap-beijing.myqcloud.com/hy-func/_lib/config/volcano-face-change-api.json",
"save_path": "_lib/config/volcano-face-change-api.json"
},
# _service
{
"url": "https://hy-sdk-config-1305323352.cos.ap-beijing.myqcloud.com/hy-func/_service/config/func_error_conf.json",
"save_path": "_service/utils/config/func_error_conf.json"
},
{
"url": "https://hy-sdk-config-1305323352.cos.ap-beijing.myqcloud.com/hy-func/_service/config/service_config.json",
"save_path": "_service/utils/config/service_config.json"
},
# --------- 模型部分
# 模型部分存储在Notion文档当中
# https://www.notion.so/HY-func-cc6cc41ba6e94b36b8fa5f5d67d1683f
],
"weights": "https://www.notion.so/HY-func-cc6cc41ba6e94b36b8fa5f5d67d1683f"
}
class HivisionaiUtils(object):
"""
本类为一些基本工具类,包含代码复用相关内容
"""
@staticmethod
def get_client():
"""获取cos客户端对象"""
def get_secret():
# 首先判断cloud_config_save下是否存在
if not os.path.exists(HivisionaiParams.cloud_config_save):
print("Downloading user_config...")
resp = requests.get(HivisionaiParams.user_url)
open(HivisionaiParams.cloud_config_save, "wb").write(resp.content)
config = json.load(open(HivisionaiParams.cloud_config_save, "r"))
return config["secret_id"], config["secret_key"]
# todo 接入HY-Auth-Sync
secret_id, secret_key = get_secret()
return CosS3Client(CosConfig(Region=HivisionaiParams.region, Secret_id=secret_id, Secret_key=secret_key))
def get_all_versions(self):
"""获取云端的所有版本号"""
def getAllVersion_base():
"""
返回cos存储桶内部的某个文件夹的内部名称
ps:如果需要修改默认的存储桶配置,请在代码运行的时候加入代码 s.bucket = 存储桶名称 (s是对象实例)
返回的内容存储在response["Content"],不过返回的数据大小是有限制的,具体内容还是请看官方文档。
Returns:
[版本列表]
"""
resp = client.list_objects(
Bucket=HivisionaiParams.bucket,
Prefix=HivisionaiParams.zip_key,
Marker=marker
)
versions_list.extend([x["Key"].split("/")[-1].split(HivisionaiParams.file_format)[0] for x in resp["Contents"] if int(x["Size"]) > 0])
if resp['IsTruncated'] == 'false': # 接下来没有数据了,就退出
return ""
else:
return resp['NextMarker']
client = self.get_client()
marker = ""
versions_list = []
while True: # 轮询
try:
marker = getAllVersion_base()
except KeyError as e:
print(e)
raise
if len(marker) == 0: # 没有数据了
break
return versions_list
def get_newest_version(self):
"""获取最新的版本号"""
versions_list = self.get_all_versions()
# reverse=True,降序
versions_list.sort(key=lambda x: int(x.split(".")[-1]), reverse=True)
versions_list.sort(key=lambda x: int(x.split(".")[-2]), reverse=True)
versions_list.sort(key=lambda x: int(x.split(".")[-3]), reverse=True)
return versions_list[0]
def download_version(self, v):
"""
在存储桶中下载文件,将下载好的文件解压至本地
Args:
v: 版本号,x.x.x
Returns:
None
"""
file_name = v + HivisionaiParams.file_format
client = self.get_client()
print(f"Download to {HivisionaiParams.save_folder}...")
try:
resp = client.get_object(HivisionaiParams.bucket, HivisionaiParams.zip_key + "/" + file_name)
contents = resp["Body"].get_raw_stream().read()
except CosServiceError:
print(f"[{file_name}.zip] does not exist, please check your version!")
sys.exit()
if not os.path.exists(HivisionaiParams.save_folder):
os.makedirs(HivisionaiParams.save_folder)
open(os.path.join(HivisionaiParams.save_folder, file_name), "wb").write(contents)
print("Download success!")
@staticmethod
def download_dependence(path=None):
"""
一键下载HY-sdk所需要的所有依赖,需要注意的是,本方法必须在运行pip install之后使用(运行完pip install之后才会出现hivisionai文件夹)
Args:
path: 文件路径,精确到hivisionai文件夹的上一个目录,如果为None,则默认下载到python环境下hivisionai安装的目录
Returns:
下载相应内容到指定位置
"""
# print("指定的下载路径:", path) # 此时在path路径下必然存在一个hivisionai文件夹
# print("系统安装的hivisionai库的路径:", HivisionaiParams.hivisionai_path)
print("Dependence downloading...")
if path is None:
path = HivisionaiParams.hivisionai_path
# ----------------下载mtcnn模型文件
mtcnn_path = os.path.join(path, "hivisionai/hycv/mtcnn_onnx/weights")
base_url = "https://linimages.oss-cn-beijing.aliyuncs.com/"
onnx_files = ["pnet.onnx", "rnet.onnx", "onet.onnx"]
print(f"Downloading mtcnn model in {mtcnn_path}")
if not os.path.exists(mtcnn_path):
os.mkdir(mtcnn_path)
for onnx_file in onnx_files:
if not os.path.exists(os.path.join(mtcnn_path, onnx_file)):
# download onnx model
onnx_url = base_url + onnx_file
print("Downloading Onnx Model in:", onnx_url)
r = requests.get(onnx_url, stream=True)
if r.status_code == 200:
open(os.path.join(mtcnn_path, onnx_file), 'wb').write(r.content) # 将内容写入文件
print(f"Download finished -- {onnx_file}")
del r
# ----------------
print("Dependence download finished...")
class HivisionaiApps(object):
"""
本类为app对外暴露的接口,为了代码规整性,这里使用类来对暴露接口进行调整
"""
@staticmethod
def show_cloud_version():
"""查看在cos中的所有HY-sdk版本"""
print("Connect to COS...")
versions_list = hivisionai_utils.get_all_versions()
# reverse=True,降序
versions_list.sort(key=lambda x: int(x.split(".")[-1]), reverse=True)
versions_list.sort(key=lambda x: int(x.split(".")[-2]), reverse=True)
versions_list.sort(key=lambda x: int(x.split(".")[-3]), reverse=True)
if len(versions_list) == 0:
print("There is no version currently, please release it first!")
sys.exit()
versions = "The currently existing versions (Keep 10): \n"
for i, v in enumerate(versions_list):
versions += str(v) + " "
if i == 9:
break
print(versions)
@staticmethod
def upgrade(v: str, enforce: bool = False, save_cached: bool = False):
"""
自动升级HY-sdk到指定版本
Args:
v: 指定的版本号,格式为x.x.x
enforce: 是否需要强制执行更新命令
save_cached: 是否保存下载的wheel文件,默认为否
Returns:
None
"""
def check_format():
# noinspection PyBroadException
try:
major, minor, patch = v.split(".")
int(major)
int(minor)
int(patch)
except Exception as e:
print(f"Illegal version number!\n{e}")
pass
print("Upgrading, please wait a moment...")
if v == "-1":
v = hivisionai_utils.get_newest_version()
# 检查format的格式
check_format()
if v == version(HivisionaiParams.package_name) and not enforce:
print(f"Current version: {v} already exists, skip installation.")
sys.exit()
hivisionai_utils.download_version(v)
# 下载完毕(下载至save_folder),解压文件
target_zip = os.path.join(HivisionaiParams.save_folder, f"{v}.zip")
assert zipfile.is_zipfile(target_zip), "Decompression failed, and the target was not a zip file."
new_dir = target_zip.replace('.zip', '') # 解压的文件名
if os.path.exists(new_dir): # 判断文件夹是否存在
shutil.rmtree(new_dir)
os.mkdir(new_dir) # 新建文件夹
f = zipfile.ZipFile(target_zip)
f.extractall(new_dir) # 提取zip文件
print("Decompressed, begin to install...")
os.system(f'pip3 install {os.path.join(new_dir, "**.whl")}')
# 开始自动下载必要的模型依赖
hivisionai_utils.download_dependence()
# 安装完毕,如果save_cached为真,删除"$HOME/.hivisionai/sdk"内部的所有文件元素
if save_cached is True:
os.system(f'rm -rf {HivisionaiParams.save_folder}/**')
@staticmethod
def export(path):
"""
输出最新版本的文件到命令运行的path目录
Args:
path: 用户输入的路径
Returns:
输出最新的hivisionai到path目录
"""
# print(f"当前路径: {os.path.join(HivisionaiParams.getcwd, path)}")
# print(f"文件路径: {os.path.dirname(__file__)}")
export_path = os.path.join(HivisionaiParams.getcwd, path)
# 判断输出路径存不存在,如果不存在,就报错
assert os.path.exists(export_path), f"{export_path} dose not Exists!"
v = hivisionai_utils.get_newest_version()
# 下载文件到.hivisionai/sdk当中
hivisionai_utils.download_version(v)
# 下载完毕(下载至save_folder),解压文件
target_zip = os.path.join(HivisionaiParams.save_folder, f"{v}.zip")
assert zipfile.is_zipfile(target_zip), "Decompression failed, and the target was not a zip file."
new_dir = os.path.basename(target_zip.replace('.zip', '')) # 解压的文件名
new_dir = os.path.join(export_path, new_dir) # 解压的文件路径
if os.path.exists(new_dir): # 判断文件夹是否存在
shutil.rmtree(new_dir)
os.mkdir(new_dir) # 新建文件夹
f = zipfile.ZipFile(target_zip)
f.extractall(new_dir) # 提取zip文件
print("Decompressed, begin to export...")
# 强制删除bin/hivisionai和hivisionai/以及HY_sdk-**
bin_path = os.path.join(export_path, "bin")
hivisionai_path = os.path.join(export_path, "hivisionai")
sdk_path = os.path.join(export_path, "HY_sdk-**")
os.system(f"rm -rf {bin_path} {hivisionai_path} {sdk_path}")
# 删除完毕,开始export
os.system(f'pip3 install {os.path.join(new_dir, "**.whl")} -t {export_path}')
hivisionai_utils.download_dependence(export_path)
# 将下载下来的文件夹删除
os.system(f'rm -rf {target_zip} && rm -rf {new_dir}')
print("Done.")
@staticmethod
def hy_func_init(force):
"""
在HY-func目录下使用hivisionai --init,可以自动将需要的依赖下载到指定位置
不过对于比较大的模型——修复模型而言,需要手动下载
Args:
force: 如果force为True,则会强制重新下载所有的内容,包括修复模型这种比较大的模型
Returns:
程序执行完毕,会将一些必要的依赖也下载完毕
"""
cwd = HivisionaiParams.getcwd
# 判断当前文件夹是否是HY-func
dirName = os.path.basename(cwd)
assert dirName == "HY-func", "请在正确的文件目录下初始化HY-func!"
# 需要下载的内容会存放在HivisionaiParams的functionDependence变量下
functionDependence = HivisionaiParams.functionDependence
# 下载配置文件
configs = functionDependence["configs"]
print("正在下载配置文件...")
for config in configs:
if not force and os.path.exists(config['save_path']):
print(f"[pass]: {os.path.basename(config['url'])}")
continue
print(f"[Download]: {config['url']}")
resp = requests.get(config['url'])
# json文件存储在text区域,但是其他的不一定
open(os.path.join(cwd, config['save_path']), 'w').write(resp.text)
# 其他文件,提示访问notion文档
print(f"[NOTICE]: 一切准备就绪,请访问下面的文档下载剩下的模型文件:\n{functionDependence['weights']}")
@staticmethod
def hy_func_deploy(functionName: str = None, functionPath: str = None):
"""
在HY-func目录下使用此命令,并且随附功能函数的名称,就可以将HY-func的部署版放到桌面上
但是需要注意的是,本方式不适合修复功能使用,修复功能依旧需要手动制作镜像
Args:
functionName: 功能函数名称
functionPath: 需要注册的HY-func路径
Returns:
程序执行完毕,桌面会出现一个同名文件夹
"""
# 为了代码撰写的方便,这里仅仅把模型文件删除,其余配置文件保留
# 为了实现在任意位置输入hivisionai --deploy funcName都能成功,在使用前需要在.hivisionai/user_config.json中注册
# print(functionName, functionPath)
if functionPath is not None:
# 更新/添加路径
# functionPath为相对于使用路径的路径
assert os.path.basename(functionPath) == "HY-func", "所指向路径非HY-func!"
func_path = os.path.join(HivisionaiParams.getcwd, functionPath)
assert os.path.join(func_path), f"路径不存在: {func_path}"
# functionPath的路径写到user_config当中
user_config = json.load(open(HivisionaiParams.cloud_config_save, 'rb'))
user_config["func_path"] = func_path
open(HivisionaiParams.cloud_config_save, 'w').write(json.dumps(user_config))
print("HY-func全局路径保存成功!")
try:
user_config = json.load(open(HivisionaiParams.cloud_config_save, 'rb'))
func_path = user_config['func_path']
except KeyError:
return print("请先使用-p命令注册全局HY-func路径!")
# 此时func_path必然存在
# print(os.listdir(func_path))
assert functionName in os.listdir(func_path), functionName + "功能不存在!"
func_path_deploy = os.path.join(func_path, functionName)
# 开始复制文件到指定目录
# 我们默认移动到Desktop目录下,如果没有此目录,需要先创建一个
target_dir = os.path.join(HivisionaiParams.download_path, "Desktop")
assert os.path.exists(target_dir), target_dir + "文件路径不存在,你需要先创建一下!"
# 开始移动
target_dir = os.path.join(target_dir, functionName)
print("正在复制需要部署的文件...")
os.system(f"rm -rf {target_dir}")
os.system(f'cp -rf {func_path_deploy} {target_dir}')
os.system(f"cp -rf {os.path.join(func_path, '_lib')} {target_dir}")
os.system(f"cp -rf {os.path.join(func_path, '_service')} {target_dir}")
# 生成最新的hivisionai
print("正在生成hivisionai代码包...")
os.system(f'hivisionai -t {target_dir}')
# 移动完毕,删除模型文件
print("移动完毕,正在删除不需要的文件...")
# 模型文件
os.system(f"rm -rf {os.path.join(target_dir, '_lib', 'weights', '**')}")
# hivisionai生成时的多余文件
os.system(f"rm -rf {os.path.join(target_dir, 'bin')} {os.path.join(target_dir, 'HY_sdk**')}")
print("部署文件生成成功,你可以开始部署了!")
hivisionai_utils = HivisionaiUtils()
def entry_point():
parser = ArgumentParser()
# 查看版本号
parser.add_argument("-v", "--version", action="store_true", help="View the current HY-sdk version, which does not represent the final cloud version.")
# 自动更新
parser.add_argument("-u", "--upgrade", nargs='?', const="-1", type=str, help="Automatically update HY-sdk to the latest version")
# 查找云端的HY-sdk版本
parser.add_argument("-l", "--list", action="store_true", help="Find HY-sdk versions of the cloud, and keep up to ten")
# 下载云端的版本到本地路径
parser.add_argument("-t", "--export", nargs='?', const="./", help="Add a path parameter to automatically download the latest version of sdk to this path. If there are no parameters, the default is the current path")
# 强制更新附带参数,当一个功能需要强制执行一遍的时候,需要附带此参数
parser.add_argument("-f", "--force", action="store_true", help="Enforcement of other functions, execution of a single parameter is meaningless")
# 初始化HY-func
parser.add_argument("--init", action="store_true", help="Initialization HY-func")
# 部署HY-func
parser.add_argument("-d", "--deploy", nargs='?', const="-1", type=str, help="Deploy HY-func")
# 涉及注册一些自定义内容的时候,需要附带此参数,并写上自定义内容
parser.add_argument("-p", "--param", nargs='?', const="-1", type=str, help="When registering some custom content, you need to attach this parameter and write the custom content.")
args = parser.parse_args()
if args.version:
print(version(HivisionaiParams.package_name))
sys.exit()
if args.upgrade:
HivisionaiApps.upgrade(args.upgrade, args.force)
sys.exit()
if args.list:
HivisionaiApps.show_cloud_version()
sys.exit()
if args.export:
HivisionaiApps.export(args.export)
sys.exit()
if args.init:
HivisionaiApps.hy_func_init(args.force)
sys.exit()
if args.deploy:
HivisionaiApps.hy_func_deploy(args.deploy, args.param)
if __name__ == "__main__":
entry_point()
|