├── static
├── model
│ └── .500.pth
├── sketch_tmp
│ └── upload.png
├── css
│ ├── upload.svg
│ ├── canvas_style.css
│ ├── page_style.css
│ └── button.css
└── js
│ ├── gallery.js
│ ├── main.js
│ └── jquery-3.4.0.min.js
├── requirements.txt
├── example.gif
├── SketchTriplet
├── out_feat_flickr15k_1904041458
│ └── .feat_photo.npz
├── flickr15k_dataset.py
├── retrieval.py
└── SketchTriplet_half_sharing.py
├── download_prerequisites.sh
├── LICENSE
├── templates
├── canvas.html
└── panel.html
├── README.md
└── controller.py
/static/model/.500.pth:
--------------------------------------------------------------------------------
1 | # should place 500.pth here
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | torch>=1.3.1
2 | torchvision>=0.4.2
3 | Flask>=1.1.1
4 |
--------------------------------------------------------------------------------
/example.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SeaRhyme7th/SketchAPP/HEAD/example.gif
--------------------------------------------------------------------------------
/SketchTriplet/out_feat_flickr15k_1904041458/.feat_photo.npz:
--------------------------------------------------------------------------------
1 | # should place feat_photo.npz here
--------------------------------------------------------------------------------
/static/sketch_tmp/upload.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SeaRhyme7th/SketchAPP/HEAD/static/sketch_tmp/upload.png
--------------------------------------------------------------------------------
/static/css/upload.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/download_prerequisites.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/bash
2 |
3 | # offline features
4 | wget --no-check-certificate 'https://docs.google.com/uc?export=download&id=1Z1fbJrNnjD7VrubYBCtfweYNoMjH1uEW' -O feat_photo.npz
5 | mv feat_photo.npz SketchTriplet/out_feat_flickr15k_1904041458/
6 |
7 | # image dataset
8 | wget --load-cookies /tmp/cookies.txt "https://docs.google.com/uc?export=download&confirm=$(wget --quiet --save-cookies /tmp/cookies.txt --keep-session-cookies --no-check-certificate 'https://docs.google.com/uc?export=download&id=1PqzIO-OWTeEAl3Hs5tRavRs6-qZ8OmXb' -O- | sed -rn 's/.*confirm=([0-9A-Za-z_]+).*/\1\n/p')&id=1PqzIO-OWTeEAl3Hs5tRavRs6-qZ8OmXb" -O resize_img.zip
9 | rm /tmp/cookies.txt
10 | unzip resize_img.zip
11 | mv resize_img/* static/images/dataset/
12 | rm -r resize_img
13 |
14 | # offline model
15 | wget --no-check-certificate 'https://docs.google.com/uc?export=download&id=1oUDCTENBzdBok7rjB_B8zHE3mwdw_0ve' -O 500.pth
16 | mv 500.pth static/model/
17 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 SeaRhyme7th
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/SketchTriplet/flickr15k_dataset.py:
--------------------------------------------------------------------------------
1 | import os
2 | import os.path
3 | from torch.utils.data import Dataset
4 |
5 |
6 | class flickr15k_dataset_lite(Dataset):
7 | def __init__(self, root='./static/images'):
8 | self.root = root
9 | self.gt_path = os.path.join(self.root, 'groundtruth')
10 | self.img_set_path = os.path.join(self.root, 'dataset')
11 | self.gt = {}
12 |
13 | for i in range(1, 34):
14 | """flickr15k has 33 classes, silly way"""
15 | self.gt[str(i)] = []
16 | file = open(self.gt_path)
17 | for line in file:
18 | sketch_cls = line.split()[0]
19 | img_path = line.split()[1][:-4] + '.png'
20 | img_cls = img_path.split('/')[0]
21 | img_name = img_path.split('/')[1][:-4]
22 | img_path = os.path.join(self.img_set_path, img_path)
23 | # check img exist
24 | if os.path.exists(img_path):
25 | self.gt[sketch_cls].append((img_path, img_cls, img_name))
26 | file.close()
27 |
28 | self.datapath = []
29 | for i in range(1, 34):
30 | item = str(i)
31 | for fn in self.gt[item]:
32 | # item: class number
33 | # f[0]: file absolute path
34 | # f[1]: class name
35 | # f[2]: file name
36 | self.datapath.append((fn[1], item, fn[0], fn[2]))
37 |
38 | def __getitem__(self, idx):
39 | photo = self.datapath[idx]
40 | return photo
41 |
42 | def __len__(self):
43 | return len(self.datapath)
44 |
--------------------------------------------------------------------------------
/static/js/gallery.js:
--------------------------------------------------------------------------------
1 | let json_info = document.getElementById("json_info").value;
2 | let show_img_pannel = document.getElementById("show_img_pannel")
3 |
4 | json_info = JSON.parse(json_info);
5 |
6 | for(var i = 0; i < 18; i++){
7 | var obj_li = document.createElement("li");
8 | obj_li.id = "li" + i;
9 | var obj_img = '
';
10 | obj_li.innerHTML = '' + obj_img + '' + '' + json_info[i].name + '';
11 | show_img_pannel.appendChild(obj_li);
12 | }
13 |
14 | var index = 0;
15 |
16 | window.onload = function () {/*当页面加载完成后再执行这部分js代码*/
17 | var info = document.getElementById("info");
18 | info.innerHTML = (index+1)+"/5";
19 | }
20 |
21 | function getNextImg() {
22 |
23 | var info = document.getElementById("info");
24 | index++;
25 |
26 | if(index > 4){ /*当图片已经翻到最后一张时,跳转到第一张图片*/
27 | index = 0;
28 | }
29 |
30 | for(var i = 0; i < 18; i++){
31 | var ii = i + index*18
32 | var obj_li = document.getElementById("li" + i);
33 | var obj_img = '
';
34 | obj_li.innerHTML = '' + obj_img + '' + '' + json_info[ii].name + '';
35 | }
36 |
37 | info.innerHTML = (index+1)+"/5";
38 | }
39 |
40 | function getProImg() {
41 |
42 | var info = document.getElementById("info");
43 | index--;
44 |
45 | if(index < 0){ /*当图片已经翻到第张时,跳转到最后一张图片*/
46 | index = 4;
47 | }
48 |
49 | for(var i = 0; i < 18; i++){
50 | var ii = i + index*18
51 | var obj_li = document.getElementById("li" + i);
52 | var obj_img = '
';
53 | obj_li.innerHTML = '' + obj_img + '' + '' + json_info[ii].name + '';
54 | }
55 |
56 | info.innerHTML = (index+1)+"/5";
57 | }
--------------------------------------------------------------------------------
/templates/canvas.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SBIR DEMO
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
18 |
19 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/templates/panel.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SBIR DEMO
6 |
7 |
8 |
9 |
10 |
11 |
15 |
16 |
17 |
18 |
Input sketch
19 |
20 |
 }})
24 |
25 |
26 |
27 |
28 |
Retrieval results
29 |
34 |
35 |
36 |
37 | |
38 | 上一张
39 |
40 | |
41 |
42 | 1/5 |
43 |
44 | 下一张
45 |
46 | |
47 |
48 | 返回
49 | |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/SketchTriplet/retrieval.py:
--------------------------------------------------------------------------------
1 | from PIL import Image
2 | import numpy as np
3 | import torchvision.transforms as transforms
4 | from torch.autograd import Variable
5 | import torch
6 |
7 |
8 | def extract_feat_sketch(net, sketch_src):
9 | img_size = 256
10 | normalize = transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010))
11 | transform = transforms.Compose([
12 | # transforms.RandomHorizontalFlip(),
13 | transforms.Resize((img_size, img_size)),
14 | transforms.ToTensor(),
15 | normalize
16 | ])
17 | sketch_src = transform(sketch_src)
18 | sketch = Variable(sketch_src.unsqueeze(0))
19 | if torch.cuda.is_available():
20 | sketch = sketch.cuda()
21 | feat = net.get_branch_sketch(sketch)
22 | feat = feat.cpu().data.numpy()
23 | return feat
24 |
25 |
26 | def get_real_path(retrieval_list):
27 | retrieval_list = list(retrieval_list)
28 | real_list_set = []
29 | for i in range(5):
30 | real_list = []
31 | for j in range(18):
32 | ori_path = retrieval_list[i * 18 + j]
33 | real_path = './images/dataset/' + ori_path.split('/')[-2] + '/' + ori_path.split('/')[-1][:-4] + '.png'
34 | name = ori_path.split('/')[-2] + '/' + ori_path.split('/')[-1][:-4]
35 | real_list.append((real_path, name))
36 | real_list_set.append(real_list)
37 |
38 | # convert to dic for json dump
39 | path_dic = []
40 | for i in range(90):
41 | tmp = {}
42 | ori_path = retrieval_list[i]
43 | real_path = './images/dataset/' + ori_path.split('/')[-2] + '/' + ori_path.split('/')[-1][:-4] + '.png'
44 | name = ori_path.split('/')[-2] + '/' + ori_path.split('/')[-1][:-4]
45 | tmp['path'] = real_path
46 | tmp['name'] = name
47 | path_dic.append(tmp)
48 |
49 | return real_list_set, path_dic
50 |
51 |
52 | def retrieval(net, sketch_path):
53 | sketch_src = Image.open(sketch_path).convert('RGB')
54 | feat_s = extract_feat_sketch(net, sketch_src)
55 | feat_photo_path = './SketchTriplet/out_feat_flickr15k_1904041458/feat_photo.npz'
56 | feat_photo = np.load(feat_photo_path)
57 |
58 | feat_p = feat_photo['feat']
59 | cls_name_p = feat_photo['cls_name']
60 | cls_num_p = feat_photo['cls_num']
61 | path_p = feat_photo['path']
62 | name_p = feat_photo['name']
63 |
64 | dist_l2 = np.sqrt(np.sum(np.square(feat_s - feat_p), 1))
65 | order = np.argsort(dist_l2)
66 | order_path_p = path_p[order]
67 |
68 | return get_real_path(order_path_p)
69 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SketchAPP
2 |
3 | A simple Sketch-Based Image Retrieval web application. The system uses Flask for web application framework, and PyTorch for retrieving.
4 |
5 | ## Dependency
6 |
7 | - pytorch 0.4.0 with torchvision 0.2.1 or higher (test with pytorch 1.5.0+cpu and torchvision 0.6.0+cpu)
8 | - python 3.6.4
9 | - flask 0.12.2
10 | - anaconda 4.4.10 recommend
11 |
12 | ## Retrieving
13 |
14 | The system applies Siamese network based on T.Bui et al's paper "[Compact Descriptors for Sketch-based Image Retrieval using a Triplet loss Convolutional Neural Network](https://doi.org/10.1016/j.cviu.2017.06.007)"[[Repo](https://github.com/TuBui/Triplet_Loss_SBIR)|[Page](http://www.cvssp.org/data/Flickr25K/CVIU16.html)]. All retrieving code comes from [jjkislele/SketchTriplet](https://github.com/jjkislele/SketchTriplet).
15 |
16 | ## Web application demo
17 |
18 | The web application has 2 pages, including 'canvas' and result 'panel'.
19 |
20 | In 'canvas' page, you can draw freehand sketch on canvas. 6 icon buttons lie at the bottom of the page. Each button implies its function by its shape: the first pencil-like button is used for drawing. The second eraser-like button is used to clean lines. The third brush-like button is used to clear the screen. The back button is used to withdraw by step. The diskette-like button is used to store the sketch when you complete your drawing. The last upload button is used to load your local sketch file if you don't want to draw by yourself. The right-side slider is used for changing your stroke size.
21 |
22 | In 'panel' page, you can get 18 per page, 90 in total retrival results. Every retrieval result has its name with classfication.
23 |
24 | 
25 |
26 | ## How to deploy
27 |
28 | Run `download_prerequisites.sh` to download the dataset, the offline feature of the dataset, and the retrieval model from Google drive.
29 |
30 | - offline feature [Google drive](https://drive.google.com/open?id=1Z1fbJrNnjD7VrubYBCtfweYNoMjH1uEW)
31 | - dataset [Google drive](https://drive.google.com/open?id=1PqzIO-OWTeEAl3Hs5tRavRs6-qZ8OmXb)
32 | - retrieval model [Google drive](https://drive.google.com/open?id=1oUDCTENBzdBok7rjB_B8zHE3mwdw_0ve)
33 |
34 | Put them in the right place. And run python script
35 |
36 | ```bash
37 | python controller.py
38 | ```
39 |
40 | Open your Internet browser. Visit `http://localhost:5000/canvas`, and enjoy it!
41 |
42 | ## Special thanks
43 |
44 | 1. Canvas designer [@zhoushuozh](https://github.com/zhoushuozh/drawingborad)
45 | 2. Retieval model [@TuBui](https://github.com/TuBui/Triplet_Loss_SBIR)
46 |
--------------------------------------------------------------------------------
/static/css/canvas_style.css:
--------------------------------------------------------------------------------
1 | *{margin:0; padding: 0;user-select: none;}
2 | body{overflow:hidden}
3 | #drawing-board{background: white;position:fixed; display: block;cursor: crosshair;}
4 | .header {position: fixed; text-align: center; background: #1abc9c; color: white; display: flex;width:100%;}
5 | .tools{position: fixed;left:0;bottom: 30px; width:100%;display: flex;justify-content: center;text-align: center}
6 | .tools button{border-radius: 50%;width: 50px;height: 50px; background-color: rgba(255,255,255,0.7);border: 1px solid #eee;outline: none;cursor: pointer;box-sizing: border-box;margin: 0 10px;text-align: center;color:#ccc;line-height: 50px;box-shadow:0 0 8px rgba(0,0,0,0.1); transition: 0.3s;}
7 | .tools button.active,.tools button:active{box-shadow: 0 0 15px #00CCFF; color:#00CCFF;}
8 | .tools button i{font-size: 24px;}
9 | .color-group{position:fixed;width: 30px;left: 30px;top:50%;transform: translate(0,-150px)}
10 | .color-group ul{list-style: none;}
11 | .color-group ul li{width: 30px;height: 30px;margin: 10px 0;border-radius: 50%;box-sizing: border-box;border:3px solid white;box-shadow: 0 0 8px rgba(0,0,0,0.2);cursor: pointer;transition: 0.3s;}
12 | .color-group ul li.active{box-shadow:0 0 15px #00CCFF;}
13 | #range-wrap{position: fixed;top: 50%;right:30px;width: 30px;height: 150px;margin-top: -75px;}
14 | #range-wrap input{transform: rotate(-90deg);width: 150px;height: 20px;margin: 0;transform-origin: 75px 75px; border-radius: 15px;-webkit-appearance: none;outline: none;position: relative;}
15 | #range-wrap input::after{display: block;content:"";width:0;height: 0;border:5px solid transparent;
16 | border-right:150px solid #00CCFF;border-left-width:0;position: absolute;left: 0;top: 5px;border-radius:15px; z-index: 0; }
17 | #range-wrap input[type=range]::-webkit-slider-thumb,#range-wrap input[type=range]::-moz-range-thumb{-webkit-appearance: none;}
18 | #range-wrap input[type=range]::-webkit-slider-runnable-track,#range-wrap input[type=range]::-moz-range-track {height: 10px;border-radius: 10px;box-shadow: none;}
19 | #range-wrap input[type=range]::-webkit-slider-thumb{-webkit-appearance: none;height: 20px;width: 20px;margin-top: -1px;background: #ffffff;border-radius: 50%;box-shadow: 0 0 8px #00CCFF;position: relative;z-index: 999;}
20 |
21 | @media screen and (max-width: 768px) {
22 | .header{bottom:auto;top:20px;}
23 | .tools{bottom:auto;top:20px;}
24 | .tools button{width: 35px;height: 35px;line-height: 35px;margin-bottom: 15px;box-shadow:0 0 5px rgba(0,0,0,0.1);}
25 | .tools button.active,.tools button:active{box-shadow: 0 0 5px #00CCFF;}
26 | .tools button i{font-size: 18px;}
27 | .tools #swatches{display: none}
28 | .color-group{left: 0;top:auto;bottom: 20px;display: flex;width:100%;justify-content: center;text-align: center;transform: translate(0,0)}
29 | .color-group ul li{display: inline-block;margin:0 5px;}
30 | .color-group ul li.active{box-shadow:0 0 10px #00CCFF;}
31 | #range-wrap{right:auto;left: 20px;}
32 | }
--------------------------------------------------------------------------------
/controller.py:
--------------------------------------------------------------------------------
1 | # coding:utf-8
2 | from flask import Flask, render_template, request
3 | import json
4 | import os
5 | import time
6 | import base64
7 | import torch
8 | from datetime import timedelta
9 |
10 | # SketchTriplet network--------------------
11 | from SketchTriplet.SketchTriplet_half_sharing import BranchNet
12 | from SketchTriplet.SketchTriplet_half_sharing import SketchTriplet as SketchTriplet_hs
13 | from SketchTriplet.flickr15k_dataset import flickr15k_dataset_lite
14 | from SketchTriplet.retrieval import retrieval
15 |
16 |
17 | def load_model_retrieval():
18 | net_dict_path = './static/model/500.pth'
19 | branch_net = BranchNet() # for photography edge
20 | net = SketchTriplet_hs(branch_net)
21 | if torch.cuda.is_available():
22 | net.load_state_dict(torch.load(net_dict_path))
23 | net = net.cuda()
24 | else:
25 | net.load_state_dict(torch.load(net_dict_path, map_location=torch.device('cpu')))
26 | net.eval()
27 | return net
28 |
29 |
30 | # Retrieval model and dataset definition
31 | flickr15k_dataset = flickr15k_dataset_lite()
32 | retrieval_net = load_model_retrieval()
33 |
34 | # SketchAPP definition
35 | app = Flask(__name__, template_folder='templates', static_folder='static')
36 | app.send_file_max_age_default = timedelta(seconds=1)
37 |
38 |
39 | @app.route('/canvas', methods=['POST', 'GET'])
40 | def upload():
41 | if request.method == 'POST':
42 | sketch_src = request.form.get("sketchUpload")
43 | upload_flag = request.form.get("uploadFlag")
44 | sketch_src_2 = None
45 | if upload_flag:
46 | sketch_src_2 = request.files["uploadSketch"]
47 | if sketch_src:
48 | flag = 1
49 | elif sketch_src_2:
50 | flag = 2
51 | else:
52 | return render_template('canvas.html')
53 |
54 | basepath = os.path.dirname(__file__)
55 | upload_path = os.path.join(basepath, 'static/sketch_tmp', 'upload.png')
56 | if flag == 1:
57 | # base64 image decode
58 | sketch = base64.b64decode(sketch_src[22:])
59 | user_input = request.form.get("name")
60 | file = open(upload_path, "wb")
61 | file.write(sketch)
62 | file.close()
63 |
64 | elif flag == 2:
65 | # upload sketch
66 | sketch_src_2.save(upload_path)
67 | user_input = request.form.get("name")
68 |
69 | # for retrieval
70 | retrieval_list, real_path = retrieval(retrieval_net, upload_path)
71 | real_path = json.dumps(real_path)
72 |
73 | return render_template('panel.html', userinput=user_input, val1=time.time(), upload_src=sketch_src,
74 | retrieval_list=retrieval_list,
75 | json_info=real_path)
76 |
77 | return render_template('canvas.html')
78 |
79 |
80 | @app.route('/canvas')
81 | def homepage():
82 | return render_template('canvas.html')
83 |
84 |
85 | if __name__ == '__main__':
86 | # open debug mode
87 | app.run(debug=True)
88 |
--------------------------------------------------------------------------------
/static/css/page_style.css:
--------------------------------------------------------------------------------
1 | * {
2 | box-sizing: border-box;
3 | }
4 |
5 | /* body 样式 */
6 | body {
7 | font-family: Arial;
8 | margin: 0;
9 | }
10 |
11 | /* 标题字体加大 */
12 | .header h1 {
13 | font-size: 40px;
14 | }
15 |
16 | .container{
17 | margin:0 auto;
18 | width:300px;
19 | }
20 |
21 | /* 导航 */
22 | .navbar {
23 | overflow: hidden;
24 | background-color: #333;
25 | }
26 |
27 | /* 导航栏样式 */
28 | .navbar a {
29 | float: left;
30 | display: block;
31 | color: white;
32 | text-align: center;
33 | padding: 14px 20px;
34 | text-decoration: none;
35 | }
36 |
37 | /* 右侧链接*/
38 | .navbar a.right {
39 | float: right;
40 | }
41 |
42 | /* 鼠标移动到链接的颜色 */
43 | .navbar a:hover {
44 | background-color: #ddd;
45 | color: black;
46 | }
47 |
48 | /* 列容器 */
49 | .row {
50 | display: -ms-flexbox; /* IE10 */
51 | display: flex;
52 | -ms-flex-wrap: wrap; /* IE10 */
53 | flex-wrap: wrap;
54 | }
55 |
56 | /* 创建两个列 */
57 | /* 边栏 */
58 | .side {
59 | -ms-flex: 20%; /* IE10 */
60 | flex: 20%;
61 | background-color: #f1f1f1;
62 | padding: 20px 0px 10px 20px;
63 | }
64 |
65 | /* 主要的内容区域 */
66 | .main {
67 | -ms-flex: 80%; /* IE10 */
68 | flex: 80%;
69 | background-color: white;
70 | padding: 20px;
71 | }
72 |
73 | /* 测试图片 */
74 | .fakeimg {
75 | background-color: #aaa;
76 | width: 100%;
77 | padding: 20px;
78 | }
79 |
80 | /* 底部 */
81 | .footer {
82 | padding: 20px;
83 | text-align: center;
84 | background: #ddd;
85 | }
86 |
87 | /* 响应式布局 - 在屏幕设备宽度尺寸小于 700px 时, 让两栏上下堆叠显示 */
88 | @media screen and (max-width: 700px) {
89 | .row {
90 | flex-direction: column;
91 | }
92 | }
93 |
94 | /* 响应式布局 - 在屏幕设备宽度尺寸小于 400px 时, 让导航栏目上下堆叠显示 */
95 | @media screen and (max-width: 400px) {
96 | .navbar a {
97 | float: none;
98 | width: 100%;
99 | }
100 | }
101 |
102 |
103 | .pic_list_con{
104 | width:1100px;
105 | height: 550px;
106 | border:1px solid #ddd;
107 | /*多出来的ul要剪掉*/
108 | overflow:hidden;
109 | }
110 | .pic_list_con h3{
111 | width:918px;
112 | height:50px;
113 | border-bottom: 1px solid #ddd;
114 | /*是h3居中,不是h3中的文字居中哦!*/
115 | margin:0 auto;
116 | }
117 | .pic_list_con h3 span{
118 | /*内联元素转为内联块元素,支持全部样式*/
119 | display:inline-block;
120 | height:50px;
121 | border-bottom: 2px solid red;
122 | font: 18px/50px 'Microsoft YaHei UI';
123 | padding:0 15px;
124 | }
125 | .pic_list_con ul{
126 | width: 1050px;
127 | margin:20px 0 13px 20px;
128 | }
129 | .pic_list_con ul li{
130 | width:160px;
131 | height:160px;
132 | list-style-type: none;
133 | float:left;
134 | margin:5px 10px 5px 5px;
135 | background: white;
136 | border: 1px solid #ddd;
137 | box-shadow: 2px 2px 3px rgba(50,50,50,0.4);
138 |
139 | }
140 | .pic_list_con ul li img{
141 | height: 130px;
142 | max-width: 150px;
143 | display:block;
144 | text-align:center;
145 | margin: 0px auto;
146 | padding: 15px 0 0 0;
147 | }
148 | .pic_list_con ul li span{
149 | display:block;
150 | line-height:20px;
151 | text-align:center;
152 | font-size:x-small;
153 | }
--------------------------------------------------------------------------------
/SketchTriplet/SketchTriplet_half_sharing.py:
--------------------------------------------------------------------------------
1 | import torch.nn as nn
2 |
3 |
4 | class BranchNet(nn.Module):
5 | def __init__(self, num_feat=100):
6 | super(BranchNet, self).__init__()
7 | self.num_feat = num_feat
8 | self.conv1 = nn.Sequential(
9 | nn.Conv2d(
10 | in_channels=3,
11 | out_channels=64,
12 | kernel_size=15,
13 | stride=3,
14 | padding=0
15 | ),
16 | nn.ReLU(),
17 | nn.MaxPool2d(
18 | kernel_size=3,
19 | stride=2
20 | )
21 | )
22 | self.conv2 = nn.Sequential(
23 | nn.Conv2d(
24 | in_channels=64,
25 | out_channels=128,
26 | kernel_size=5,
27 | stride=1,
28 | padding=0
29 | ),
30 | nn.ReLU(),
31 | nn.MaxPool2d(
32 | kernel_size=3,
33 | stride=2
34 | )
35 | )
36 | self.conv3 = nn.Sequential(
37 | nn.Conv2d(
38 | in_channels=128,
39 | out_channels=256,
40 | kernel_size=3,
41 | stride=1,
42 | padding=0
43 | ),
44 | nn.ReLU()
45 | )
46 | self.conv4 = nn.Sequential(
47 | nn.Conv2d(
48 | in_channels=256,
49 | out_channels=256,
50 | kernel_size=3,
51 | stride=1,
52 | padding=0
53 | ),
54 | nn.ReLU()
55 | )
56 | self.conv5 = nn.Sequential(
57 | nn.Conv2d(
58 | in_channels=256,
59 | out_channels=256,
60 | kernel_size=3,
61 | stride=1,
62 | padding=0
63 | ),
64 | nn.ReLU(),
65 | nn.MaxPool2d(
66 | kernel_size=3,
67 | stride=2
68 | )
69 | )
70 | self.fc6 = nn.Sequential(
71 | nn.Linear(256 * 5 * 5, 2048),
72 | nn.ReLU(),
73 | nn.Dropout(p=0.55)
74 | )
75 | self.fc7 = nn.Sequential(
76 | nn.Linear(2048, 512),
77 | nn.ReLU(),
78 | nn.Dropout(p=0.55)
79 | )
80 | self.feat = nn.Linear(512, num_feat)
81 |
82 | def forward(self, x):
83 | x = self.conv1(x)
84 | x = self.conv2(x)
85 | x = self.conv3(x)
86 | x = self.conv4(x)
87 | x = self.conv5(x)
88 | x = x.view(x.size(0), -1)
89 | x = self.fc6(x)
90 | x = self.fc7(x)
91 | x = self.feat(x)
92 | return x
93 |
94 | def get_halfsharing(self, x):
95 | x = self.conv4(x)
96 | x = self.conv5(x)
97 | x = x.view(x.size(0), -1)
98 | x = self.fc6(x)
99 | x = self.fc7(x)
100 | x = self.feat(x)
101 | return x
102 |
103 | def get_branch(self, x):
104 | return self.forward(x)
105 |
106 |
107 | class SketchTriplet(nn.Module):
108 | def __init__(self, branch_net):
109 | super(SketchTriplet, self).__init__()
110 | self.branch_net = branch_net
111 |
112 | self.conv1_a = nn.Sequential(
113 | nn.Conv2d(
114 | in_channels=3,
115 | out_channels=64,
116 | kernel_size=15,
117 | stride=3,
118 | padding=0
119 | ),
120 | nn.ReLU(),
121 | nn.MaxPool2d(
122 | kernel_size=3,
123 | stride=2
124 | )
125 | )
126 | self.conv2_a = nn.Sequential(
127 | nn.Conv2d(
128 | in_channels=64,
129 | out_channels=128,
130 | kernel_size=5,
131 | stride=1,
132 | padding=0
133 | ),
134 | nn.ReLU(),
135 | nn.MaxPool2d(
136 | kernel_size=3,
137 | stride=2
138 | )
139 | )
140 | self.conv3_a = nn.Sequential(
141 | nn.Conv2d(
142 | in_channels=128,
143 | out_channels=256,
144 | kernel_size=3,
145 | stride=1,
146 | padding=0
147 | ),
148 | nn.ReLU()
149 | )
150 |
151 | def ancSeq(self, x):
152 | x = self.conv1_a(x)
153 | x = self.conv2_a(x)
154 | x = self.conv3_a(x)
155 | # half sharing
156 | x = self.branch_net.get_halfsharing(x)
157 | return x
158 |
159 | def forward(self, anc_src, pos_src, neg_src):
160 | self.anc_src = anc_src # anchor source (sketch input)
161 | self.pos_src = pos_src # positive source (photograph edge input)
162 | self.neg_src = neg_src # negative source (photograph edge input)
163 | feat_a = self.ancSeq(self.anc_src)
164 | feat_p = self.branch_net(self.pos_src)
165 | feat_n = self.branch_net(self.neg_src)
166 | return feat_a, feat_p, feat_n
167 |
168 | def get_branch_sketch(self, x):
169 | return self.ancSeq(x)
170 |
171 | def get_branch_photo(self, x):
172 | return self.branch_net(x)
173 |
--------------------------------------------------------------------------------
/static/css/button.css:
--------------------------------------------------------------------------------
1 | .button {
2 | display: inline-block;
3 | position: relative;
4 | color: #888;
5 | text-shadow: 0 1px 0 rgba(255,255,255, 0.8);
6 | text-decoration: none;
7 | text-align: center;
8 | padding: 8px 12px;
9 | font-size: 12px;
10 | font-weight: 700;
11 | font-family: helvetica, arial, sans-serif;
12 | border-radius: 4px;
13 | border: 1px solid #bcbcbc;
14 |
15 | -webkit-box-shadow: 0 1px 3px rgba(0,0,0,0.12);
16 | box-shadow: 0 1px 3px rgba(0,0,0,0.12);
17 |
18 | background-image: -webkit-linear-gradient(top, rgba(255,255,255,1) 0%,rgba(239,239,239,1) 60%,rgba(225,223,226,1) 100%);
19 | background-image: -moz-linear-gradient(top, rgba(255,255,255,1) 0%,rgba(239,239,239,1) 60%,rgba(225,223,226,1) 100%);
20 | background-image: -o-linear-gradient(top, rgba(255,255,255,1) 0%,rgba(239,239,239,1) 60%,rgba(225,223,226,1) 100%);
21 | background-image: -ms-linear-gradient(top, rgba(255,255,255,1) 0%,rgba(239,239,239,1) 60%,rgba(225,223,226,1) 100%);
22 | background-image: linear-gradient(top, rgba(255,255,255,1) 0%,rgba(239,239,239,1) 60%,rgba(225,223,226,1) 100%);
23 | }
24 |
25 | .button:hover {
26 | color: #555;
27 | }
28 |
29 | .button:active,.button:active:after,.button:active:before {
30 | -webkit-box-shadow: none;
31 | box-shadow: none;
32 | }
33 |
34 | /* Back Button */
35 | .button.back {
36 | border-left: none;
37 | }
38 |
39 | .button.back:after {
40 | content: '';
41 | position: absolute;
42 | height: 50%;
43 | width: 15px;
44 | border-left: 1px solid #bcbcbc;
45 |
46 | background-image: -webkit-linear-gradient(top, rgba(255,255,255,0) 0%,rgba(255,255,255,1) 1%,rgba(240,240,240,1) 100%);
47 | background-image: -moz-linear-gradient(top, rgba(255,255,255,0) 0%,rgba(255,255,255,1) 1%,rgba(240,240,240,1) 100%);
48 | background-image: -o-linear-gradient(top, rgba(255,255,255,0) 0%,rgba(255,255,255,1) 1%,rgba(240,240,240,1) 100%);
49 | background-image: -ms-linear-gradient(top, rgba(255,255,255,0) 0%,rgba(255,255,255,1) 1%,rgba(240,240,240,1) 100%);
50 | background-image: linear-gradient(top, rgba(255,255,255,0) 0%,rgba(255,255,255,1) 1%,rgba(240,240,240,1) 100%);
51 | left: -5px;
52 | top: 1px;
53 |
54 | -webkit-transform: skew(-35deg, 0);
55 | -moz-transform: skew(-35deg, 0);
56 | -o-transform: skew(-35deg, 0);
57 | -ms-transform: skew(-35deg, 0);
58 | transform: skew(-35deg, 0);
59 | }
60 |
61 | .button.back:before {
62 | content: '';
63 | position: absolute;
64 | height: 48%;
65 | width: 15px;
66 | border-left: 1px solid #bcbcbc;
67 | bottom: 1px;
68 | left: -5px;
69 |
70 | -webkit-transform: skew(35deg, 0);
71 | -moz-transform: skew(35deg, 0);
72 | -o-transform: skew(35deg, 0);
73 | -ms-transform: skew(35deg, 0);
74 | transform: skew(35deg, 0);
75 |
76 | background-image: -webkit-linear-gradient(top, rgba(240,240,240,1) 0%,rgba(239,239,239,1) 10%,rgba(225,223,226,1) 100%);
77 | background-image: -moz-linear-gradient(top, rgba(240,240,240,1) 0%,rgba(239,239,239,1) 10%,rgba(225,223,226,1) 100%);
78 | background-image: -o-linear-gradient(top, rgba(240,240,240,1) 0%,rgba(239,239,239,1) 10%,rgba(225,223,226,1) 100%);
79 | background-image: -ms-linear-gradient(top, rgba(240,240,240,1) 0%,rgba(239,239,239,1) 10%,rgba(225,223,226,1) 100%);
80 | background-image: linear-gradient(top, rgba(240,240,240,1) 0%,rgba(239,239,239,1) 10%,rgba(225,223,226,1) 100%);
81 |
82 | -webkit-box-shadow: -2px 1px 2px rgba(100,100,100,0.1);
83 | box-shadow: -2px 1px 2px rgba(100,100,100,0.1);
84 | }
85 |
86 | /* Next Button */
87 | .button.next {
88 | border-right: none;
89 | }
90 |
91 | .button.next:after {
92 | content: '';
93 | position: absolute;
94 | height: 48%;
95 | width: 15px;
96 | border-right: 1px solid #bcbcbc;
97 |
98 | background-image: -webkit-linear-gradient(top, rgba(240,240,240,1) 0%,rgba(239,239,239,1) 10%,rgba(225,223,226,1) 100%);
99 | background-image: -moz-linear-gradient(top, rgba(240,240,240,1) 0%,rgba(239,239,239,1) 10%,rgba(225,223,226,1) 100%);
100 | background-image: -o-linear-gradient(top, rgba(240,240,240,1) 0%,rgba(239,239,239,1) 10%,rgba(225,223,226,1) 100%);
101 | background-image: -ms-linear-gradient(top, rgba(240,240,240,1) 0%,rgba(239,239,239,1) 10%,rgba(225,223,226,1) 100%);
102 | background-image: linear-gradient(top, rgba(240,240,240,1) 0%,rgba(239,239,239,1) 10%,rgba(225,223,226,1) 100%);
103 | right: -5px;
104 | bottom: 1px;
105 |
106 | -webkit-transform: skew(-35deg, 0);
107 | -moz-transform: skew(-35deg, 0);
108 | -o-transform: skew(-35deg, 0);
109 | -ms-transform: skew(-35deg, 0);
110 | transform: skew(-35deg, 0);
111 |
112 | -webkit-box-shadow: 2px 1px 2px rgba(100,100,100,0.1);
113 | box-shadow: 2px 1px 2px rgba(100,100,100,0.1);
114 | }
115 |
116 | .button.next:before {
117 | content: '';
118 | position: absolute;
119 |
120 | background-image: -webkit-linear-gradient(top, rgba(255,255,255,0) 0%,rgba(255,255,255,1) 1%,rgba(240,240,240,1) 100%);
121 | background-image: -moz-linear-gradient(top, rgba(255,255,255,0) 0%,rgba(255,255,255,1) 1%,rgba(240,240,240,1) 100%);
122 | background-image: -o-linear-gradient(top, rgba(255,255,255,0) 0%,rgba(255,255,255,1) 1%,rgba(240,240,240,1) 100%);
123 | background-image: -ms-linear-gradient(top, rgba(255,255,255,0) 0%,rgba(255,255,255,1) 1%,rgba(240,240,240,1) 100%);
124 | background-image: linear-gradient(top, rgba(255,255,255,0) 0%,rgba(255,255,255,1) 1%,rgba(240,240,240,1) 100%);
125 | height: 50%;
126 | width: 15px;
127 | border-right: 1px solid #bcbcbc;
128 | top: 1px;
129 | right: -5px;
130 |
131 | -webkit-transform: skew(35deg, 0);
132 | -moz-transform: skew(35deg, 0);
133 | -o-transform: skew(35deg, 0);
134 | -ms-transform: skew(35deg, 0);
135 | transform: skew(35deg, 0);
136 | }
--------------------------------------------------------------------------------
/static/js/main.js:
--------------------------------------------------------------------------------
1 | let canvas = document.getElementById("drawing-board");
2 | let ctx = canvas.getContext("2d");
3 | let eraser = document.getElementById("eraser");
4 | let brush = document.getElementById("brush");
5 | let reSetCanvas = document.getElementById("clear");
6 | let aColorBtn = document.getElementsByClassName("color-item");
7 | let save = document.getElementById("save");
8 | let undo = document.getElementById("undo");
9 | let range = document.getElementById("range");
10 |
11 | let upload = document.getElementById("upload");
12 | let uploadSketch = document.getElementById("uploadSketch");
13 | let upload_form = document.getElementById("upload_form");
14 | let uploadFlag = document.getElementById("uploadFlag")
15 |
16 | let clear = false;
17 | let activeColor = 'black';
18 | let lWidth = 4;
19 |
20 | autoSetSize(canvas);
21 |
22 | setCanvasBg('white');
23 |
24 | listenToUser(canvas);
25 |
26 | getColor();
27 |
28 | //window.onbeforeunload = function(){
29 | // return "Reload site?";
30 | //};
31 |
32 | function autoSetSize(canvas) {
33 | canvasSetSize();
34 |
35 | function canvasSetSize() {
36 | let pageWidth = document.documentElement.clientWidth;
37 | let pageHeight = document.documentElement.clientHeight;
38 |
39 | canvas.width = pageWidth;
40 | canvas.height = pageHeight;
41 | }
42 |
43 | window.onresize = function () {
44 | canvasSetSize();
45 | }
46 | }
47 |
48 | function setCanvasBg(color) {
49 | ctx.fillStyle = color;
50 | ctx.fillRect(0, 0, canvas.width, canvas.height);
51 | ctx.fillStyle = "black";
52 | }
53 |
54 | function listenToUser(canvas) {
55 | let painting = false;
56 | let lastPoint = {x: undefined, y: undefined};
57 |
58 | if (document.body.ontouchstart !== undefined) {
59 | canvas.ontouchstart = function (e) {
60 | this.firstDot = ctx.getImageData(0, 0, canvas.width, canvas.height);//在这里储存绘图表面
61 | saveData(this.firstDot);
62 | painting = true;
63 | let x = e.touches[0].clientX;
64 | let y = e.touches[0].clientY;
65 | lastPoint = {"x": x, "y": y};
66 | ctx.save();
67 | drawCircle(x, y, 0);
68 | };
69 | canvas.ontouchmove = function (e) {
70 | if (painting) {
71 | let x = e.touches[0].clientX;
72 | let y = e.touches[0].clientY;
73 | let newPoint = {"x": x, "y": y};
74 | drawLine(lastPoint.x, lastPoint.y, newPoint.x, newPoint.y);
75 | lastPoint = newPoint;
76 | }
77 | };
78 |
79 | canvas.ontouchend = function () {
80 | painting = false;
81 | }
82 | } else {
83 | canvas.onmousedown = function (e) {
84 | this.firstDot = ctx.getImageData(0, 0, canvas.width, canvas.height);//在这里储存绘图表面
85 | saveData(this.firstDot);
86 | painting = true;
87 | let x = e.clientX;
88 | let y = e.clientY;
89 | lastPoint = {"x": x, "y": y};
90 | ctx.save();
91 | drawCircle(x, y, 0);
92 | };
93 | canvas.onmousemove = function (e) {
94 | if (painting) {
95 | let x = e.clientX;
96 | let y = e.clientY;
97 | let newPoint = {"x": x, "y": y};
98 | drawLine(lastPoint.x, lastPoint.y, newPoint.x, newPoint.y,clear);
99 | lastPoint = newPoint;
100 | }
101 | };
102 |
103 | canvas.onmouseup = function () {
104 | painting = false;
105 | };
106 |
107 | canvas.mouseleave = function () {
108 | painting = false;
109 | }
110 | }
111 | }
112 |
113 | function drawCircle(x, y, radius) {
114 | ctx.save();
115 | ctx.beginPath();
116 | ctx.arc(x, y, radius, 0, Math.PI * 2);
117 | ctx.fill();
118 | if (clear) {
119 | ctx.clip();
120 | ctx.clearRect(0,0,canvas.width,canvas.height);
121 | ctx.restore();
122 | }
123 | }
124 |
125 | function drawLine(x1, y1, x2, y2) {
126 | ctx.lineWidth = lWidth;
127 | ctx.lineCap = "round";
128 | ctx.lineJoin = "round";
129 | if (clear) {
130 | ctx.save();
131 | ctx.globalCompositeOperation = "destination-out";
132 | ctx.moveTo(x1, y1);
133 | ctx.lineTo(x2, y2);
134 | ctx.stroke();
135 | ctx.closePath();
136 | ctx.clip();
137 | ctx.clearRect(0,0,canvas.width,canvas.height);
138 | ctx.restore();
139 | }else{
140 | ctx.moveTo(x1, y1);
141 | ctx.lineTo(x2, y2);
142 | ctx.stroke();
143 | ctx.closePath();
144 | }
145 | }
146 |
147 | range.onchange = function(){
148 | lWidth = this.value;
149 | };
150 |
151 | eraser.onclick = function () {
152 | clear = true;
153 | this.classList.add("active");
154 | brush.classList.remove("active");
155 | };
156 |
157 | brush.onclick = function () {
158 | clear = false;
159 | this.classList.add("active");
160 | eraser.classList.remove("active");
161 | };
162 |
163 | reSetCanvas.onclick = function () {
164 | ctx.clearRect(0, 0, canvas.width, canvas.height);
165 | setCanvasBg('white');
166 | };
167 |
168 | save.onclick = function () {
169 | let imgUrl = canvas.toDataURL("image/png");
170 | // let saveA = document.createElement("a");
171 | // document.body.appendChild(saveA);
172 | // saveA.href = imgUrl;
173 | // saveA.download = "sketch" + (new Date).getTime();
174 | // saveA.target = "_blank";
175 | // saveA.click();
176 | document.getElementById("sketchUpload").value=imgUrl;
177 |
178 | };
179 |
180 | upload.onclick = function () {
181 | uploadFlag.val = "1";
182 | uploadSketch.click();
183 | };
184 |
185 | uploadSketch.onchange = function () {
186 | upload_form.submit();
187 | };
188 |
189 | function getColor(){
190 | for (let i = 0; i < aColorBtn.length; i++) {
191 | aColorBtn[i].onclick = function () {
192 | for (let i = 0; i < aColorBtn.length; i++) {
193 | aColorBtn[i].classList.remove("active");
194 | this.classList.add("active");
195 | activeColor = this.style.backgroundColor;
196 | ctx.fillStyle = activeColor;
197 | ctx.strokeStyle = activeColor;
198 | }
199 | }
200 | }
201 | }
202 |
203 | let historyDeta = [];
204 |
205 | function saveData (data) {
206 | (historyDeta.length === 10) && (historyDeta.shift());// 上限为储存10步,太多了怕挂掉
207 | historyDeta.push(data);
208 | }
209 |
210 | undo.onclick = function(){
211 | if(historyDeta.length < 1) return false;
212 | ctx.putImageData(historyDeta[historyDeta.length - 1], 0, 0);
213 | historyDeta.pop()
214 | };
--------------------------------------------------------------------------------
/static/js/jquery-3.4.0.min.js:
--------------------------------------------------------------------------------
1 | /*! jQuery v3.4.0 | (c) JS Foundation and other contributors | jquery.org/license */
2 | !function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],E=C.document,r=Object.getPrototypeOf,s=t.slice,g=t.concat,u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType},x=function(e){return null!=e&&e===e.window},c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.4.0",k=function(e,t){return new k.fn.init(e,t)},p=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g;function d(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp($),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+$),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\([\\da-f]{1,6}"+M+"?|("+M+")|.)","ig"),ne=function(e,t,n){var r="0x"+t-65536;return r!=r||n?t:r<0?String.fromCharCode(r+65536):String.fromCharCode(r>>10|55296,1023&r|56320)},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(m.childNodes),m.childNodes),t[m.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&((e?e.ownerDocument||e:m)!==C&&T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&U.test(t)){(s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=k),o=(l=h(t)).length;while(o--)l[o]="#"+s+" "+xe(l[o]);c=l.join(","),f=ee.test(t)&&ye(e.parentNode)||e}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===k&&e.removeAttribute("id")}}}return g(t.replace(B,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[k]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e.namespaceURI,n=(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:m;return r!==C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),m!==C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=k,!C.getElementsByName||!C.getElementsByName(k).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+k+"-]").length||v.push("~="),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+k+"+*").length||v.push(".#.+[+~]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",$)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},D=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)===(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e===C||e.ownerDocument===m&&y(m,e)?-1:t===C||t.ownerDocument===m&&y(m,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e===C?-1:t===C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]===m?-1:s[r]===m?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if((e.ownerDocument||e)!==C&&T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=p[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&p(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?k.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?k.grep(e,function(e){return e===n!==r}):"string"!=typeof n?k.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(k.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||q,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:L.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof k?t[0]:t,k.merge(this,k.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),D.test(r[1])&&k.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(k):k.makeArray(e,this)}).prototype=k.fn,q=k(E);var H=/^(?:parents|prev(?:Until|All))/,O={children:!0,contents:!0,next:!0,prev:!0};function P(e,t){while((e=e[t])&&1!==e.nodeType);return e}k.fn.extend({has:function(e){var t=k(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i,ge={option:[1,""],thead:[1,""],col:[2,""],tr:[2,""],td:[3,""],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&N(e,t)?k.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;nx",y.noCloneChecked=!!me.cloneNode(!0).lastChild.defaultValue;var Te=/^key/,Ce=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,Ee=/^([^.]*)(?:\.(.+)|)/;function ke(){return!0}function Se(){return!1}function Ae(e,t){return e===function(){try{return E.activeElement}catch(e){}}()==("focus"===t)}function Ne(e,t,n,r,i,o){var a,s;if("object"==typeof t){for(s in"string"!=typeof n&&(r=r||n,n=void 0),t)Ne(e,s,n,r,t[s],o);return e}if(null==r&&null==i?(i=n,r=n=void 0):null==i&&("string"==typeof n?(i=r,r=void 0):(i=r,r=n,n=void 0)),!1===i)i=Se;else if(!i)return e;return 1===o&&(a=i,(i=function(e){return k().off(e),a.apply(this,arguments)}).guid=a.guid||(a.guid=k.guid++)),e.each(function(){k.event.add(this,t,i,r,n)})}function De(e,i,o){o?(Q.set(e,i,!1),k.event.add(e,i,{namespace:!1,handler:function(e){var t,n,r=Q.get(this,i);if(1&e.isTrigger&&this[i]){if(r)(k.event.special[i]||{}).delegateType&&e.stopPropagation();else if(r=s.call(arguments),Q.set(this,i,r),t=o(this,i),this[i](),r!==(n=Q.get(this,i))||t?Q.set(this,i,!1):n=void 0,r!==n)return e.stopImmediatePropagation(),e.preventDefault(),n}else r&&(Q.set(this,i,k.event.trigger(k.extend(r.shift(),k.Event.prototype),r,this)),e.stopImmediatePropagation())}})):k.event.add(e,i,ke)}k.event={global:{},add:function(t,e,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,v=Q.get(t);if(v){n.handler&&(n=(o=n).handler,i=o.selector),i&&k.find.matchesSelector(ie,i),n.guid||(n.guid=k.guid++),(u=v.events)||(u=v.events={}),(a=v.handle)||(a=v.handle=function(e){return"undefined"!=typeof k&&k.event.triggered!==e.type?k.event.dispatch.apply(t,arguments):void 0}),l=(e=(e||"").match(R)||[""]).length;while(l--)d=g=(s=Ee.exec(e[l])||[])[1],h=(s[2]||"").split(".").sort(),d&&(f=k.event.special[d]||{},d=(i?f.delegateType:f.bindType)||d,f=k.event.special[d]||{},c=k.extend({type:d,origType:g,data:r,handler:n,guid:n.guid,selector:i,needsContext:i&&k.expr.match.needsContext.test(i),namespace:h.join(".")},o),(p=u[d])||((p=u[d]=[]).delegateCount=0,f.setup&&!1!==f.setup.call(t,r,h,a)||t.addEventListener&&t.addEventListener(d,a)),f.add&&(f.add.call(t,c),c.handler.guid||(c.handler.guid=n.guid)),i?p.splice(p.delegateCount++,0,c):p.push(c),k.event.global[d]=!0)}},remove:function(e,t,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,v=Q.hasData(e)&&Q.get(e);if(v&&(u=v.events)){l=(t=(t||"").match(R)||[""]).length;while(l--)if(d=g=(s=Ee.exec(t[l])||[])[1],h=(s[2]||"").split(".").sort(),d){f=k.event.special[d]||{},p=u[d=(r?f.delegateType:f.bindType)||d]||[],s=s[2]&&new RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),a=o=p.length;while(o--)c=p[o],!i&&g!==c.origType||n&&n.guid!==c.guid||s&&!s.test(c.namespace)||r&&r!==c.selector&&("**"!==r||!c.selector)||(p.splice(o,1),c.selector&&p.delegateCount--,f.remove&&f.remove.call(e,c));a&&!p.length&&(f.teardown&&!1!==f.teardown.call(e,h,v.handle)||k.removeEvent(e,d,v.handle),delete u[d])}else for(d in u)k.event.remove(e,d+t[l],n,r,!0);k.isEmptyObject(u)&&Q.remove(e,"handle events")}},dispatch:function(e){var t,n,r,i,o,a,s=k.event.fix(e),u=new Array(arguments.length),l=(Q.get(this,"events")||{})[s.type]||[],c=k.event.special[s.type]||{};for(u[0]=s,t=1;t\x20\t\r\n\f]*)[^>]*)\/>/gi,qe=/