ml_backend/ 是一个 label-studio-ml 后端:Label Studio 打开任务时调 /predict, 后端跑 YOLO 并把框转成 Label Studio 的百分比坐标返回,人工只需修正。 - model.py:FilmDefectDetector。权重懒加载且只加载一次(Flask 多线程,加锁); 控件名从项目的 labeling config 读取,不写死;model_version 带权重的修改时间, 换权重后 Label Studio 能区分新旧预测。 - convert.py:坐标转换,刻意不 import label_studio_ml,好在主环境里单测。 - fit():每条标注累加计数,达到 RETRAIN_AFTER 就起子进程执行 RETRAIN_COMMAND 并立刻返回——训练几十分钟,同步做会卡住 Label Studio。 - _wsgi.py 由官方模板生成,ruff 跳过,便于跟随上游。 必须用独立虚拟环境:label-studio-ml 会把 jinja2 降到 3.0.3,而 label-studio 1.23 需要 3.1,装进主环境会直接弄坏 Label Studio。PyPI 上的 1.0.9 是 2023 年的旧版, 要装 GitHub 上的现行版本。 本地用 COCO 权重端到端验证过:/health、/predict(bus.jpg 出 10 个框、坐标换算正确)、 /webhook(第 3 条标注触发重训命令并清零计数)。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
126 lines
3.7 KiB
Python
126 lines
3.7 KiB
Python
import os
|
|
import argparse
|
|
import json
|
|
import logging
|
|
import logging.config
|
|
|
|
# Set a default log level if LOG_LEVEL is not defined
|
|
log_level = os.getenv("LOG_LEVEL", "INFO")
|
|
|
|
logging.config.dictConfig({
|
|
"version": 1,
|
|
"disable_existing_loggers": False, # Prevent overriding existing loggers
|
|
"formatters": {
|
|
"standard": {
|
|
"format": "[%(asctime)s] [%(levelname)s] [%(name)s::%(funcName)s::%(lineno)d] %(message)s"
|
|
}
|
|
},
|
|
"handlers": {
|
|
"console": {
|
|
"class": "logging.StreamHandler",
|
|
"level": log_level,
|
|
"stream": "ext://sys.stdout",
|
|
"formatter": "standard"
|
|
}
|
|
},
|
|
"root": {
|
|
"level": log_level,
|
|
"handlers": [
|
|
"console"
|
|
],
|
|
"propagate": True
|
|
}
|
|
})
|
|
|
|
from label_studio_ml.api import init_app
|
|
from model import FilmDefectDetector
|
|
|
|
|
|
_DEFAULT_CONFIG_PATH = os.path.join(os.path.dirname(__file__), 'config.json')
|
|
|
|
|
|
def get_kwargs_from_config(config_path=_DEFAULT_CONFIG_PATH):
|
|
if not os.path.exists(config_path):
|
|
return dict()
|
|
with open(config_path) as f:
|
|
config = json.load(f)
|
|
assert isinstance(config, dict)
|
|
return config
|
|
|
|
|
|
if __name__ == "__main__":
|
|
parser = argparse.ArgumentParser(description='Label studio')
|
|
parser.add_argument(
|
|
'-p', '--port', dest='port', type=int, default=9090,
|
|
help='Server port')
|
|
parser.add_argument(
|
|
'--host', dest='host', type=str, default='0.0.0.0',
|
|
help='Server host')
|
|
parser.add_argument(
|
|
'--kwargs', '--with', dest='kwargs', metavar='KEY=VAL', nargs='+', type=lambda kv: kv.split('='),
|
|
help='Additional LabelStudioMLBase model initialization kwargs')
|
|
parser.add_argument(
|
|
'-d', '--debug', dest='debug', action='store_true',
|
|
help='Switch debug mode')
|
|
parser.add_argument(
|
|
'--log-level', dest='log_level', choices=['DEBUG', 'INFO', 'WARNING', 'ERROR'], default=log_level,
|
|
help='Logging level')
|
|
parser.add_argument(
|
|
'--model-dir', dest='model_dir', default=os.path.dirname(__file__),
|
|
help='Directory where models are stored (relative to the project directory)')
|
|
parser.add_argument(
|
|
'--check', dest='check', action='store_true',
|
|
help='Validate model instance before launching server')
|
|
parser.add_argument('--basic-auth-user',
|
|
default=os.environ.get('ML_SERVER_BASIC_AUTH_USER', None),
|
|
help='Basic auth user')
|
|
|
|
parser.add_argument('--basic-auth-pass',
|
|
default=os.environ.get('ML_SERVER_BASIC_AUTH_PASS', None),
|
|
help='Basic auth pass')
|
|
|
|
args = parser.parse_args()
|
|
|
|
# setup logging level
|
|
if args.log_level:
|
|
logging.root.setLevel(args.log_level)
|
|
|
|
def isfloat(value):
|
|
try:
|
|
float(value)
|
|
return True
|
|
except ValueError:
|
|
return False
|
|
|
|
def parse_kwargs():
|
|
param = dict()
|
|
for k, v in args.kwargs:
|
|
if v.isdigit():
|
|
param[k] = int(v)
|
|
elif v == 'True' or v == 'true':
|
|
param[k] = True
|
|
elif v == 'False' or v == 'false':
|
|
param[k] = False
|
|
elif isfloat(v):
|
|
param[k] = float(v)
|
|
else:
|
|
param[k] = v
|
|
return param
|
|
|
|
kwargs = get_kwargs_from_config()
|
|
|
|
if args.kwargs:
|
|
kwargs.update(parse_kwargs())
|
|
|
|
if args.check:
|
|
print('Check "' + FilmDefectDetector.__name__ + '" instance creation..')
|
|
model = FilmDefectDetector(**kwargs)
|
|
|
|
app = init_app(model_class=FilmDefectDetector, basic_auth_user=args.basic_auth_user, basic_auth_pass=args.basic_auth_pass)
|
|
|
|
app.run(host=args.host, port=args.port, debug=args.debug)
|
|
|
|
else:
|
|
# for uWSGI use
|
|
app = init_app(model_class=FilmDefectDetector)
|