修复KeyError: CONTENT-LENGTH,架设AI上色服务PaintsChainer,在Python 3.7版本下的方法

PaintsChainer是另一款基于机器学习的上色服务。在成功架设style2paints(V3)之后,尝试架设PaintsChainer进行功能对比。
以下是PaintsChainer的源代码:
https://github.com/pfnet/PaintsChainer
PaintsChainer同样使用CUDA,和style2paints在假设上有类似之处。但是它上传图片使用的Post方式却存在问题,在Python 3.6下这个问题不会发生。但如果使用新版本Python(笔者使用的是3.7.4)会导致如下错误代码:
File "...\lib\cgi.py", line 220, in parse_multipart
headers['Content-Length'] = pdict['CONTENT-LENGTH']
KeyError: 'CONTENT-LENGTH'
这是由于在Python 3.7的cgi.py中,在pdict下将CONTENT-LENGTH设置了为POST时Header内的必需字段。目前关于CONTENT-LENGTH还没有官方的介绍文档,但是在函数的介绍中,有提到它和Header原本的content-length不同。
目前由本人找到的,临时的解决办法是修改POST方法,姑且将content-length赋值给CONTENT-LENGTH放在Header中进行欺骗。事实证明这样做可以成功运行PaintsChainer。具体方法如下:
在server.py中,找到函数parse_POST,加入下面这行:
pdict['CONTENT-LENGTH'] = int(self.headers['content-length'])
这样就可以解决对CONTENT-LENGTH字段的需要。然后,找到函数do_POST,有下面这行:
id_str = re.sub(r'\W+', '', id_str.decode())
这估计是历史遗留问题,作者写代码的时候还是2017年。在最新的Python版本中,str字段在编码(encode)之前是不能解码(decode)的。而经过测试,这个id_str并不需要在Python 3.7.4下进行额外的解码操作。因此改成下面的:
id_str = re.sub(r'\W+', '', id_str())
至此顺利运行(修改后完整的server.py文件在文章末尾)。
另外,该服务调用CUDA时,使用的是CuPy而不是NumPy。而CuPy默认的缓存路径是用户文件夹路径,也就是:
C:\Users\用户名\AppData
而CuPy在读取时,默认仅支持ASCII码。这意味着,当你的用户名文件夹包含非英文字符的时候,CuPy读取缓存会报错。
至于你要修改指定编码为utf-8,还是干脆新建一个英文名称的本地账户,就看自己的能力和需求了。
完整的修改后的server.py文件如下:
#!/usr/bin/env python
import http.server
import sys
import time
import re
import argparse
from cgi import parse_header, parse_multipart
from urllib.parse import parse_qs
# sys.path.append('./cgi-bin/wnet')
sys.path.append('./cgi-bin/paint_x2_unet')
import cgi_exe
sys.path.append('./cgi-bin/helpers')
from platformAdapter import OSHelper
class MyHandler(http.server.CGIHTTPRequestHandler):
t = []
def __init__(self, req, client_addr, server):
OSHelper.detect_environment()
http.server.CGIHTTPRequestHandler.__init__(
self, req, client_addr, server)
def parse_POST(self):
ctype, pdict = parse_header(self.headers['content-type'])
pdict['boundary'] = bytes(pdict['boundary'], "utf-8")
# Added 1 debug line here
pdict['CONTENT-LENGTH'] = int(self.headers['content-length'])
if ctype == 'multipart/form-data':
postvars = parse_multipart(self.rfile, pdict)
elif ctype == 'application/x-www-form-urlencoded':
length = int(self.headers['content-length'])
postvars = parse_qs(
self.rfile.read(length),
keep_blank_values=1)
else:
postvars = {}
return postvars
def log_t(self):
if( args.debug ):
self.t.append(time.time())
return
def print_log(self):
if( args.debug ):
for i, j in zip(self.t, self.t[1:]):
print("time [sec]", j - i)
self.t = []
return
def do_POST(self):
self.log_t()
form = self.parse_POST()
self.log_t()
if "id" in form:
id_str = form["id"][0]
# Changed 1 line here
# original line:
# id_str = re.sub(r'\W+', '', id_str.decode())
id_str = re.sub(r'\W+', '', id_str())
else:
self.ret_result(False)
return
if( re.search('/post/*', self.path) != None ):
self.post_process( form, id_str )
elif ( re.search('/paint/*', self.path) != None ):
self.paint_process( form, id_str )
else:
self.ret_result(False)
return
def post_process(self, form, id_str):
if "line" in form:
bin1 = form["line"][0]
fout1 = open("./images/line/" + id_str + ".png", 'wb')
fout1.write(bin1)
fout1.close()
else:
self.ret_result(False)
return
if "ref" in form:
bin2 = form["ref"][0]
fout2 = open("./images/ref/" + id_str + ".png", 'wb')
fout2.write(bin2)
fout2.close()
else:
self.ret_result(False)
return
self.log_t()
self.ret_result(True)
self.log_t()
self.print_log()
return
def paint_process(self, form, id_str):
blur = 0
if "blur" in form:
blur = form["blur"][0].decode()
try:
blur = int(blur)
except ValueError:
blur = 0
self.log_t()
painter.colorize(id_str, form["step"][0].decode() if "step" in form else "C", blur=blur)
self.log_t()
self.ret_result(True)
self.log_t()
self.print_log()
return
def ret_result(self, success):
if success:
content = bytes(
"{ 'message':'The command Completed Successfully' , 'Status':'200 OK','success':true , 'used':" + str(args.gpu) + "}", "UTF-8")
self.send_response(200)
else:
content = bytes(
"{ 'message':'The command Failed' , 'Status':'503 NG','success':false , 'used':" + str(args.gpu) + "}", "UTF-8")
self.send_response(503)
self.send_header("Content-type", "application/json")
self.send_header("Content-Length", len(content))
self.send_header("Access-Control-Allow-Origin", "*") # hard coding...
self.end_headers()
self.wfile.write(content)
self.log_t()
# set args
if "__main__" in __name__:
parser = argparse.ArgumentParser(
description='chainer line drawing colorization server')
parser.add_argument('--gpu', '-g', type=int, default=0,
help='GPU ID (negative value indicates CPU)')
parser.add_argument('--mode', '-m', default="stand_alone",
help='set process mode')
# other mode "post_server" "paint_server"
parser.add_argument('--port', '-p', type=int, default=8000,
help='using port')
parser.add_argument('--debug', dest='debug', action='store_true')
parser.set_defaults(feature=False)
parser.add_argument('--host', '-ho', default='localhost',
help='using host')
args = parser.parse_args()
if args.mode == "stand_alone" or args.mode == "paint_server":
print('GPU: {}'.format(args.gpu))
painter = cgi_exe.Painter(gpu=args.gpu)
httpd = http.server.HTTPServer((args.host, args.port), MyHandler)
print('serving at', args.host, ':', args.port, )
httpd.serve_forever()
欢迎来GitHub讨论:
https://github.com/pfnet/PaintsChainer/issues/150