jonigata commited on
Commit
b82e8b8
1 Parent(s): 293c885

initial commit

Browse files
Dockerfile ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # read the doc: https://huggingface.co/docs/hub/spaces-sdks-docker
2
+ # you will also find guides on how best to write your Dockerfile
3
+
4
+ FROM python:3.9
5
+
6
+ WORKDIR /code
7
+
8
+ COPY ./requirements.txt /code/requirements.txt
9
+
10
+ RUN apt-get update && apt-get upgrade -y && apt-get install -y libgl1-mesa-dev
11
+ RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
12
+ RUN mim install mmcv-full==1.7.0
13
+ RUN pip install mmdet mmpose
14
+
15
+ # Set up a new user named "user" with user ID 1000
16
+ RUN useradd -m -u 1000 user
17
+
18
+ # Switch to the "user" user
19
+ USER user
20
+
21
+ # Set home to the user's home directory
22
+ ENV HOME=/home/user \
23
+ PATH=/home/user/.local/bin:$PATH
24
+
25
+ # Set the working directory to the user's home directory
26
+ WORKDIR $HOME/app
27
+
28
+ # Copy the current directory contents into the container at $HOME/app setting the owner to the user
29
+ COPY --chown=user . $HOME/app
30
+
31
+ CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "7860"]
abgr.py ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import huggingface_hub
3
+ import onnxruntime as rt
4
+ import numpy as np
5
+ import cv2
6
+
7
+ providers = ['CUDAExecutionProvider', 'CPUExecutionProvider']
8
+ model_path = huggingface_hub.hf_hub_download("skytnt/anime-seg", "isnetis.onnx")
9
+ rmbg_model = rt.InferenceSession(model_path, providers=providers)
10
+
11
+ def get_mask(img, s=1024):
12
+ img = (img / 255).astype(np.float32)
13
+ h, w = h0, w0 = img.shape[:-1]
14
+ h, w = (s, int(s * w / h)) if h > w else (int(s * h / w), s)
15
+ ph, pw = s - h, s - w
16
+ img_input = np.zeros([s, s, 3], dtype=np.float32)
17
+ img_input[ph // 2:ph // 2 + h, pw // 2:pw // 2 + w] = cv2.resize(img, (w, h))
18
+ img_input = np.transpose(img_input, (2, 0, 1))
19
+ img_input = img_input[np.newaxis, :]
20
+ mask = rmbg_model.run(None, {'img': img_input})[0][0]
21
+ mask = np.transpose(mask, (1, 2, 0))
22
+ mask = mask[ph // 2:ph // 2 + h, pw // 2:pw // 2 + w]
23
+ mask = cv2.resize(mask, (w0, h0))[:, :, np.newaxis]
24
+ return mask
25
+
26
+
27
+ def remove_bg(img):
28
+ mask = get_mask(img)
29
+ img = (mask * img + 255 * (1 - mask)).astype(np.uint8)
30
+ mask = (mask * 255).astype(np.uint8)
31
+ img = np.concatenate([img, mask], axis=2, dtype=np.uint8)
32
+ mask = mask.repeat(3, axis=2)
33
+ return mask, img
34
+
35
+ def split_image(img):
36
+ mask = get_mask(img)
37
+ inv_mask = 1 - mask
38
+ fg = (mask * img + 255 * inv_mask).astype(np.uint8)
39
+ bg = (inv_mask * img + 255 * mask).astype(np.uint8)
40
+ mask = (mask * 255).astype(np.uint8)
41
+ inv_mask = (inv_mask * 255).astype(np.uint8)
42
+ fg = np.concatenate([fg, mask], axis=2, dtype=np.uint8)
43
+ bg = np.concatenate([bg, inv_mask], axis=2, dtype=np.uint8)
44
+ mask = mask.repeat(3, axis=2)
45
+ return mask, fg, bg
46
+
47
+
external/coco.py ADDED
@@ -0,0 +1,181 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ dataset_info = dict(
2
+ dataset_name='coco',
3
+ paper_info=dict(
4
+ author='Lin, Tsung-Yi and Maire, Michael and '
5
+ 'Belongie, Serge and Hays, James and '
6
+ 'Perona, Pietro and Ramanan, Deva and '
7
+ r'Doll{\'a}r, Piotr and Zitnick, C Lawrence',
8
+ title='Microsoft coco: Common objects in context',
9
+ container='European conference on computer vision',
10
+ year='2014',
11
+ homepage='http://cocodataset.org/',
12
+ ),
13
+ keypoint_info={
14
+ 0:
15
+ dict(name='nose', id=0, color=[51, 153, 255], type='upper', swap=''),
16
+ 1:
17
+ dict(
18
+ name='left_eye',
19
+ id=1,
20
+ color=[51, 153, 255],
21
+ type='upper',
22
+ swap='right_eye'),
23
+ 2:
24
+ dict(
25
+ name='right_eye',
26
+ id=2,
27
+ color=[51, 153, 255],
28
+ type='upper',
29
+ swap='left_eye'),
30
+ 3:
31
+ dict(
32
+ name='left_ear',
33
+ id=3,
34
+ color=[51, 153, 255],
35
+ type='upper',
36
+ swap='right_ear'),
37
+ 4:
38
+ dict(
39
+ name='right_ear',
40
+ id=4,
41
+ color=[51, 153, 255],
42
+ type='upper',
43
+ swap='left_ear'),
44
+ 5:
45
+ dict(
46
+ name='left_shoulder',
47
+ id=5,
48
+ color=[0, 255, 0],
49
+ type='upper',
50
+ swap='right_shoulder'),
51
+ 6:
52
+ dict(
53
+ name='right_shoulder',
54
+ id=6,
55
+ color=[255, 128, 0],
56
+ type='upper',
57
+ swap='left_shoulder'),
58
+ 7:
59
+ dict(
60
+ name='left_elbow',
61
+ id=7,
62
+ color=[0, 255, 0],
63
+ type='upper',
64
+ swap='right_elbow'),
65
+ 8:
66
+ dict(
67
+ name='right_elbow',
68
+ id=8,
69
+ color=[255, 128, 0],
70
+ type='upper',
71
+ swap='left_elbow'),
72
+ 9:
73
+ dict(
74
+ name='left_wrist',
75
+ id=9,
76
+ color=[0, 255, 0],
77
+ type='upper',
78
+ swap='right_wrist'),
79
+ 10:
80
+ dict(
81
+ name='right_wrist',
82
+ id=10,
83
+ color=[255, 128, 0],
84
+ type='upper',
85
+ swap='left_wrist'),
86
+ 11:
87
+ dict(
88
+ name='left_hip',
89
+ id=11,
90
+ color=[0, 255, 0],
91
+ type='lower',
92
+ swap='right_hip'),
93
+ 12:
94
+ dict(
95
+ name='right_hip',
96
+ id=12,
97
+ color=[255, 128, 0],
98
+ type='lower',
99
+ swap='left_hip'),
100
+ 13:
101
+ dict(
102
+ name='left_knee',
103
+ id=13,
104
+ color=[0, 255, 0],
105
+ type='lower',
106
+ swap='right_knee'),
107
+ 14:
108
+ dict(
109
+ name='right_knee',
110
+ id=14,
111
+ color=[255, 128, 0],
112
+ type='lower',
113
+ swap='left_knee'),
114
+ 15:
115
+ dict(
116
+ name='left_ankle',
117
+ id=15,
118
+ color=[0, 255, 0],
119
+ type='lower',
120
+ swap='right_ankle'),
121
+ 16:
122
+ dict(
123
+ name='right_ankle',
124
+ id=16,
125
+ color=[255, 128, 0],
126
+ type='lower',
127
+ swap='left_ankle')
128
+ },
129
+ skeleton_info={
130
+ 0:
131
+ dict(link=('left_ankle', 'left_knee'), id=0, color=[0, 255, 0]),
132
+ 1:
133
+ dict(link=('left_knee', 'left_hip'), id=1, color=[0, 255, 0]),
134
+ 2:
135
+ dict(link=('right_ankle', 'right_knee'), id=2, color=[255, 128, 0]),
136
+ 3:
137
+ dict(link=('right_knee', 'right_hip'), id=3, color=[255, 128, 0]),
138
+ 4:
139
+ dict(link=('left_hip', 'right_hip'), id=4, color=[51, 153, 255]),
140
+ 5:
141
+ dict(link=('left_shoulder', 'left_hip'), id=5, color=[51, 153, 255]),
142
+ 6:
143
+ dict(link=('right_shoulder', 'right_hip'), id=6, color=[51, 153, 255]),
144
+ 7:
145
+ dict(
146
+ link=('left_shoulder', 'right_shoulder'),
147
+ id=7,
148
+ color=[51, 153, 255]),
149
+ 8:
150
+ dict(link=('left_shoulder', 'left_elbow'), id=8, color=[0, 255, 0]),
151
+ 9:
152
+ dict(
153
+ link=('right_shoulder', 'right_elbow'), id=9, color=[255, 128, 0]),
154
+ 10:
155
+ dict(link=('left_elbow', 'left_wrist'), id=10, color=[0, 255, 0]),
156
+ 11:
157
+ dict(link=('right_elbow', 'right_wrist'), id=11, color=[255, 128, 0]),
158
+ 12:
159
+ dict(link=('left_eye', 'right_eye'), id=12, color=[51, 153, 255]),
160
+ 13:
161
+ dict(link=('nose', 'left_eye'), id=13, color=[51, 153, 255]),
162
+ 14:
163
+ dict(link=('nose', 'right_eye'), id=14, color=[51, 153, 255]),
164
+ 15:
165
+ dict(link=('left_eye', 'left_ear'), id=15, color=[51, 153, 255]),
166
+ 16:
167
+ dict(link=('right_eye', 'right_ear'), id=16, color=[51, 153, 255]),
168
+ 17:
169
+ dict(link=('left_ear', 'left_shoulder'), id=17, color=[51, 153, 255]),
170
+ 18:
171
+ dict(
172
+ link=('right_ear', 'right_shoulder'), id=18, color=[51, 153, 255])
173
+ },
174
+ joint_weights=[
175
+ 1., 1., 1., 1., 1., 1., 1., 1.2, 1.2, 1.5, 1.5, 1., 1., 1.2, 1.2, 1.5,
176
+ 1.5
177
+ ],
178
+ sigmas=[
179
+ 0.026, 0.025, 0.025, 0.035, 0.035, 0.079, 0.079, 0.072, 0.072, 0.062,
180
+ 0.062, 0.107, 0.107, 0.087, 0.087, 0.089, 0.089
181
+ ])
external/default_runtime.py ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ checkpoint_config = dict(interval=10)
2
+
3
+ log_config = dict(
4
+ interval=50,
5
+ hooks=[
6
+ dict(type='TextLoggerHook'),
7
+ # dict(type='TensorboardLoggerHook')
8
+ # dict(type='PaviLoggerHook') # for internal services
9
+ ])
10
+
11
+ log_level = 'INFO'
12
+ load_from = None
13
+ resume_from = None
14
+ dist_params = dict(backend='nccl')
15
+ workflow = [('train', 1)]
16
+
17
+ # disable opencv multithreading to avoid system being overloaded
18
+ opencv_num_threads = 0
19
+ # set multi-process start method as `fork` to speed up the training
20
+ mp_start_method = 'fork'
external/faster_rcnn_r50_fpn_coco.py ADDED
@@ -0,0 +1,182 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ checkpoint_config = dict(interval=1)
2
+ # yapf:disable
3
+ log_config = dict(
4
+ interval=50,
5
+ hooks=[
6
+ dict(type='TextLoggerHook'),
7
+ # dict(type='TensorboardLoggerHook')
8
+ ])
9
+ # yapf:enable
10
+ dist_params = dict(backend='nccl')
11
+ log_level = 'INFO'
12
+ load_from = None
13
+ resume_from = None
14
+ workflow = [('train', 1)]
15
+ # optimizer
16
+ optimizer = dict(type='SGD', lr=0.02, momentum=0.9, weight_decay=0.0001)
17
+ optimizer_config = dict(grad_clip=None)
18
+ # learning policy
19
+ lr_config = dict(
20
+ policy='step',
21
+ warmup='linear',
22
+ warmup_iters=500,
23
+ warmup_ratio=0.001,
24
+ step=[8, 11])
25
+ total_epochs = 12
26
+
27
+ model = dict(
28
+ type='FasterRCNN',
29
+ pretrained='torchvision://resnet50',
30
+ backbone=dict(
31
+ type='ResNet',
32
+ depth=50,
33
+ num_stages=4,
34
+ out_indices=(0, 1, 2, 3),
35
+ frozen_stages=1,
36
+ norm_cfg=dict(type='BN', requires_grad=True),
37
+ norm_eval=True,
38
+ style='pytorch'),
39
+ neck=dict(
40
+ type='FPN',
41
+ in_channels=[256, 512, 1024, 2048],
42
+ out_channels=256,
43
+ num_outs=5),
44
+ rpn_head=dict(
45
+ type='RPNHead',
46
+ in_channels=256,
47
+ feat_channels=256,
48
+ anchor_generator=dict(
49
+ type='AnchorGenerator',
50
+ scales=[8],
51
+ ratios=[0.5, 1.0, 2.0],
52
+ strides=[4, 8, 16, 32, 64]),
53
+ bbox_coder=dict(
54
+ type='DeltaXYWHBBoxCoder',
55
+ target_means=[.0, .0, .0, .0],
56
+ target_stds=[1.0, 1.0, 1.0, 1.0]),
57
+ loss_cls=dict(
58
+ type='CrossEntropyLoss', use_sigmoid=True, loss_weight=1.0),
59
+ loss_bbox=dict(type='L1Loss', loss_weight=1.0)),
60
+ roi_head=dict(
61
+ type='StandardRoIHead',
62
+ bbox_roi_extractor=dict(
63
+ type='SingleRoIExtractor',
64
+ roi_layer=dict(type='RoIAlign', output_size=7, sampling_ratio=0),
65
+ out_channels=256,
66
+ featmap_strides=[4, 8, 16, 32]),
67
+ bbox_head=dict(
68
+ type='Shared2FCBBoxHead',
69
+ in_channels=256,
70
+ fc_out_channels=1024,
71
+ roi_feat_size=7,
72
+ num_classes=80,
73
+ bbox_coder=dict(
74
+ type='DeltaXYWHBBoxCoder',
75
+ target_means=[0., 0., 0., 0.],
76
+ target_stds=[0.1, 0.1, 0.2, 0.2]),
77
+ reg_class_agnostic=False,
78
+ loss_cls=dict(
79
+ type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0),
80
+ loss_bbox=dict(type='L1Loss', loss_weight=1.0))),
81
+ # model training and testing settings
82
+ train_cfg=dict(
83
+ rpn=dict(
84
+ assigner=dict(
85
+ type='MaxIoUAssigner',
86
+ pos_iou_thr=0.7,
87
+ neg_iou_thr=0.3,
88
+ min_pos_iou=0.3,
89
+ match_low_quality=True,
90
+ ignore_iof_thr=-1),
91
+ sampler=dict(
92
+ type='RandomSampler',
93
+ num=256,
94
+ pos_fraction=0.5,
95
+ neg_pos_ub=-1,
96
+ add_gt_as_proposals=False),
97
+ allowed_border=-1,
98
+ pos_weight=-1,
99
+ debug=False),
100
+ rpn_proposal=dict(
101
+ nms_pre=2000,
102
+ max_per_img=1000,
103
+ nms=dict(type='nms', iou_threshold=0.7),
104
+ min_bbox_size=0),
105
+ rcnn=dict(
106
+ assigner=dict(
107
+ type='MaxIoUAssigner',
108
+ pos_iou_thr=0.5,
109
+ neg_iou_thr=0.5,
110
+ min_pos_iou=0.5,
111
+ match_low_quality=False,
112
+ ignore_iof_thr=-1),
113
+ sampler=dict(
114
+ type='RandomSampler',
115
+ num=512,
116
+ pos_fraction=0.25,
117
+ neg_pos_ub=-1,
118
+ add_gt_as_proposals=True),
119
+ pos_weight=-1,
120
+ debug=False)),
121
+ test_cfg=dict(
122
+ rpn=dict(
123
+ nms_pre=1000,
124
+ max_per_img=1000,
125
+ nms=dict(type='nms', iou_threshold=0.7),
126
+ min_bbox_size=0),
127
+ rcnn=dict(
128
+ score_thr=0.05,
129
+ nms=dict(type='nms', iou_threshold=0.5),
130
+ max_per_img=100)
131
+ # soft-nms is also supported for rcnn testing
132
+ # e.g., nms=dict(type='soft_nms', iou_threshold=0.5, min_score=0.05)
133
+ ))
134
+
135
+ dataset_type = 'CocoDataset'
136
+ data_root = 'data/coco'
137
+ img_norm_cfg = dict(
138
+ mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True)
139
+ train_pipeline = [
140
+ dict(type='LoadImageFromFile'),
141
+ dict(type='LoadAnnotations', with_bbox=True),
142
+ dict(type='Resize', img_scale=(1333, 800), keep_ratio=True),
143
+ dict(type='RandomFlip', flip_ratio=0.5),
144
+ dict(type='Normalize', **img_norm_cfg),
145
+ dict(type='Pad', size_divisor=32),
146
+ dict(type='DefaultFormatBundle'),
147
+ dict(type='Collect', keys=['img', 'gt_bboxes', 'gt_labels']),
148
+ ]
149
+ test_pipeline = [
150
+ dict(type='LoadImageFromFile'),
151
+ dict(
152
+ type='MultiScaleFlipAug',
153
+ img_scale=(1333, 800),
154
+ flip=False,
155
+ transforms=[
156
+ dict(type='Resize', keep_ratio=True),
157
+ dict(type='RandomFlip'),
158
+ dict(type='Normalize', **img_norm_cfg),
159
+ dict(type='Pad', size_divisor=32),
160
+ dict(type='DefaultFormatBundle'),
161
+ dict(type='Collect', keys=['img']),
162
+ ])
163
+ ]
164
+ data = dict(
165
+ samples_per_gpu=2,
166
+ workers_per_gpu=2,
167
+ train=dict(
168
+ type=dataset_type,
169
+ ann_file=f'{data_root}/annotations/instances_train2017.json',
170
+ img_prefix=f'{data_root}/train2017/',
171
+ pipeline=train_pipeline),
172
+ val=dict(
173
+ type=dataset_type,
174
+ ann_file=f'{data_root}/annotations/instances_val2017.json',
175
+ img_prefix=f'{data_root}/val2017/',
176
+ pipeline=test_pipeline),
177
+ test=dict(
178
+ type=dataset_type,
179
+ ann_file=f'{data_root}/annotations/instances_val2017.json',
180
+ img_prefix=f'{data_root}/val2017/',
181
+ pipeline=test_pipeline))
182
+ evaluation = dict(interval=1, metric='bbox')
external/hrnet_w48_coco_256x192.py ADDED
@@ -0,0 +1,169 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ _base_ = [
2
+ 'default_runtime.py',
3
+ 'coco.py'
4
+ ]
5
+ evaluation = dict(interval=10, metric='mAP', save_best='AP')
6
+
7
+ optimizer = dict(
8
+ type='Adam',
9
+ lr=5e-4,
10
+ )
11
+ optimizer_config = dict(grad_clip=None)
12
+ # learning policy
13
+ lr_config = dict(
14
+ policy='step',
15
+ warmup='linear',
16
+ warmup_iters=500,
17
+ warmup_ratio=0.001,
18
+ step=[170, 200])
19
+ total_epochs = 210
20
+ channel_cfg = dict(
21
+ num_output_channels=17,
22
+ dataset_joints=17,
23
+ dataset_channel=[
24
+ [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16],
25
+ ],
26
+ inference_channel=[
27
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16
28
+ ])
29
+
30
+ # model settings
31
+ model = dict(
32
+ type='TopDown',
33
+ pretrained='https://download.openmmlab.com/mmpose/'
34
+ 'pretrain_models/hrnet_w48-8ef0771d.pth',
35
+ backbone=dict(
36
+ type='HRNet',
37
+ in_channels=3,
38
+ extra=dict(
39
+ stage1=dict(
40
+ num_modules=1,
41
+ num_branches=1,
42
+ block='BOTTLENECK',
43
+ num_blocks=(4, ),
44
+ num_channels=(64, )),
45
+ stage2=dict(
46
+ num_modules=1,
47
+ num_branches=2,
48
+ block='BASIC',
49
+ num_blocks=(4, 4),
50
+ num_channels=(48, 96)),
51
+ stage3=dict(
52
+ num_modules=4,
53
+ num_branches=3,
54
+ block='BASIC',
55
+ num_blocks=(4, 4, 4),
56
+ num_channels=(48, 96, 192)),
57
+ stage4=dict(
58
+ num_modules=3,
59
+ num_branches=4,
60
+ block='BASIC',
61
+ num_blocks=(4, 4, 4, 4),
62
+ num_channels=(48, 96, 192, 384))),
63
+ ),
64
+ keypoint_head=dict(
65
+ type='TopdownHeatmapSimpleHead',
66
+ in_channels=48,
67
+ out_channels=channel_cfg['num_output_channels'],
68
+ num_deconv_layers=0,
69
+ extra=dict(final_conv_kernel=1, ),
70
+ loss_keypoint=dict(type='JointsMSELoss', use_target_weight=True)),
71
+ train_cfg=dict(),
72
+ test_cfg=dict(
73
+ flip_test=True,
74
+ post_process='default',
75
+ shift_heatmap=True,
76
+ modulate_kernel=11))
77
+
78
+ data_cfg = dict(
79
+ image_size=[192, 256],
80
+ heatmap_size=[48, 64],
81
+ num_output_channels=channel_cfg['num_output_channels'],
82
+ num_joints=channel_cfg['dataset_joints'],
83
+ dataset_channel=channel_cfg['dataset_channel'],
84
+ inference_channel=channel_cfg['inference_channel'],
85
+ soft_nms=False,
86
+ nms_thr=1.0,
87
+ oks_thr=0.9,
88
+ vis_thr=0.2,
89
+ use_gt_bbox=False,
90
+ det_bbox_thr=0.0,
91
+ bbox_file='data/coco/person_detection_results/'
92
+ 'COCO_val2017_detections_AP_H_56_person.json',
93
+ )
94
+
95
+ train_pipeline = [
96
+ dict(type='LoadImageFromFile'),
97
+ dict(type='TopDownGetBboxCenterScale', padding=1.25),
98
+ dict(type='TopDownRandomShiftBboxCenter', shift_factor=0.16, prob=0.3),
99
+ dict(type='TopDownRandomFlip', flip_prob=0.5),
100
+ dict(
101
+ type='TopDownHalfBodyTransform',
102
+ num_joints_half_body=8,
103
+ prob_half_body=0.3),
104
+ dict(
105
+ type='TopDownGetRandomScaleRotation', rot_factor=40, scale_factor=0.5),
106
+ dict(type='TopDownAffine'),
107
+ dict(type='ToTensor'),
108
+ dict(
109
+ type='NormalizeTensor',
110
+ mean=[0.485, 0.456, 0.406],
111
+ std=[0.229, 0.224, 0.225]),
112
+ dict(type='TopDownGenerateTarget', sigma=2),
113
+ dict(
114
+ type='Collect',
115
+ keys=['img', 'target', 'target_weight'],
116
+ meta_keys=[
117
+ 'image_file', 'joints_3d', 'joints_3d_visible', 'center', 'scale',
118
+ 'rotation', 'bbox_score', 'flip_pairs'
119
+ ]),
120
+ ]
121
+
122
+ val_pipeline = [
123
+ dict(type='LoadImageFromFile'),
124
+ dict(type='TopDownGetBboxCenterScale', padding=1.25),
125
+ dict(type='TopDownAffine'),
126
+ dict(type='ToTensor'),
127
+ dict(
128
+ type='NormalizeTensor',
129
+ mean=[0.485, 0.456, 0.406],
130
+ std=[0.229, 0.224, 0.225]),
131
+ dict(
132
+ type='Collect',
133
+ keys=['img'],
134
+ meta_keys=[
135
+ 'image_file', 'center', 'scale', 'rotation', 'bbox_score',
136
+ 'flip_pairs'
137
+ ]),
138
+ ]
139
+
140
+ test_pipeline = val_pipeline
141
+
142
+ data_root = 'data/coco'
143
+ data = dict(
144
+ samples_per_gpu=32,
145
+ workers_per_gpu=2,
146
+ val_dataloader=dict(samples_per_gpu=32),
147
+ test_dataloader=dict(samples_per_gpu=32),
148
+ train=dict(
149
+ type='TopDownCocoDataset',
150
+ ann_file=f'{data_root}/annotations/person_keypoints_train2017.json',
151
+ img_prefix=f'{data_root}/train2017/',
152
+ data_cfg=data_cfg,
153
+ pipeline=train_pipeline,
154
+ dataset_info={{_base_.dataset_info}}),
155
+ val=dict(
156
+ type='TopDownCocoDataset',
157
+ ann_file=f'{data_root}/annotations/person_keypoints_val2017.json',
158
+ img_prefix=f'{data_root}/val2017/',
159
+ data_cfg=data_cfg,
160
+ pipeline=val_pipeline,
161
+ dataset_info={{_base_.dataset_info}}),
162
+ test=dict(
163
+ type='TopDownCocoDataset',
164
+ ann_file=f'{data_root}/annotations/person_keypoints_val2017.json',
165
+ img_prefix=f'{data_root}/val2017/',
166
+ data_cfg=data_cfg,
167
+ pipeline=test_pipeline,
168
+ dataset_info={{_base_.dataset_info}}),
169
+ )
faster_rcnn_r50_fpn_1x_coco_20200130-047c8118.pth ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:047c8118fc5ca88ba5ae1fab72f2cd6b070501fe3af2f3cba5cfa9a89b44b03e
3
+ size 167287506
fileservice.py ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, Request, Response
2
+
3
+ filenames = ["js/poseTweek.js","js/geometry.js","js/poseAnimator.js", "js/limb.js"]
4
+ contents = '\n'.join([f"<script type='text/javascript' src='{x}'></script>" for x in filenames])
5
+
6
+ app = FastAPI()
7
+
8
+ @app.middleware("http")
9
+ async def insert_js(request: Request, call_next):
10
+ path = request.scope['path'] # get the request route
11
+ response = await call_next(request)
12
+
13
+ if path == "/":
14
+ response_body = ""
15
+ async for chunk in response.body_iterator:
16
+ response_body += chunk.decode()
17
+
18
+ some_javascript = contents
19
+
20
+ response_body = response_body.replace("</head>", some_javascript + "</head>")
21
+
22
+ del response.headers["content-length"]
23
+
24
+ return Response(
25
+ content=response_body,
26
+ status_code=response.status_code,
27
+ headers=dict(response.headers),
28
+ media_type=response.media_type
29
+ )
30
+
31
+ return response
hrnet_w48_coco_256x192-b9e0b3ab_20200708.pth ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b9e0b3ab0439cb68e166c7543e59d2587cd8d7e9acf5ea62a8378eeb82fb50e5
3
+ size 255011654
js/geometry.js ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ function dot2D(vectorA, vectorB) { return vectorA[0] * vectorB[0] + vectorA[1] * vectorB[1]; }
2
+ function cross2D(vectorA, vectorB) { return vectorA[0] * vectorB[1] - vectorA[1] * vectorB[0]; }
3
+ function magnitude2D(vector) { return Math.sqrt(dot2D(vector, vector)); }
4
+ function perpendicular2D(v) { return [v[1], -v[0]]; }
5
+ function reverse2D(v) { return [-v[0], -v[1]]; }
6
+ function normalize2D(v) {
7
+ let length = Math.sqrt(dot2D(v, v));
8
+ if (length < 0.0001) { return [0.0, 0.0]; }
9
+ return [v[0] / length, v[1] / length];
10
+ }
11
+ function signedAngle(lhs, rhs) {return Math.atan2(cross2D(lhs, rhs), dot2D(lhs, rhs));}
12
+ function angleBetween(lhs, rhs) {return Math.abs(signedAngle(lhs, rhs));}
13
+ function positiveAngle(lhs, rhs) {
14
+ var a = signedAngle(lhs, rhs);
15
+ return a < 0 ? a + Math.PI * 2 : a;
16
+ }
17
+
18
+ function getSide(A, B, C, D) {
19
+ const BA = [A[0] - B[0], A[1] - B[1]];
20
+ const BD = [D[0] - B[0], D[1] - B[1]];
21
+ const BC = [C[0] - B[0], C[1] - B[1]];
22
+ const theta0 = positiveAngle(BA, BD);
23
+ const theta1 = positiveAngle(BA, BC);
24
+
25
+ if (theta0 == 0 || theta0 == theta1) {
26
+ return 0;
27
+ } else if (theta0 < theta1) {
28
+ return 1;
29
+ } else {
30
+ return -1
31
+ }
32
+ }
33
+
34
+ function slerp2D(ca, cb, t) {
35
+ const calen = magnitude2D(ca);
36
+ const cblen = magnitude2D(cb);
37
+ const lent = calen + (cblen - calen) * t;
38
+ const cq = [ca[0] * lent / calen, ca[1] * lent / calen];
39
+
40
+ const angle = signedAngle(ca, cb) * t;
41
+ const xt = cq[0] * Math.cos(angle) - cq[1] * Math.sin(angle);
42
+ const yt = cq[0] * Math.sin(angle) + cq[1] * Math.cos(angle);
43
+
44
+ return [xt, yt];
45
+ }
js/layeredCanvas.js ADDED
@@ -0,0 +1,128 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export class LayeredCanvas {
2
+ constructor(c) {
3
+ console.log("initializeLayeredCanvas");
4
+ this.canvas = c;
5
+ this.context = canvas.getContext('2d');
6
+
7
+ this.canvas.addEventListener('mousedown', this.handleMouseDown.bind(this));
8
+ this.canvas.addEventListener('mousemove', this.handleMouseMove.bind(this));
9
+ this.canvas.addEventListener('mouseup', this.handleMouseUp.bind(this));
10
+ this.canvas.addEventListener('mouseleave', this.handleMouseLeave.bind(this));
11
+
12
+ this.layers = [];
13
+ }
14
+
15
+ cleanup() {
16
+ this.canvas.removeEventListener('mousedown', this.handleMouseDown.bind(this));
17
+ this.canvas.removeEventListener('mousemove', this.handleMouseMove.bind(this));
18
+ this.canvas.removeEventListener('mouseup', this.handleMouseUp.bind(this));
19
+ this.canvas.removeEventListener('mouseleave', this.handleMouseLeave.bind(this));
20
+ }
21
+
22
+ getCanvasSize() {
23
+ return [this.canvas.width, this.canvas.height];
24
+ }
25
+
26
+ getCanvasPosition(event) {
27
+ const rect = this.canvas.getBoundingClientRect();
28
+ const x = Math.floor(event.clientX - rect.left);
29
+ const y = Math.floor(event.clientY - rect.top);
30
+ return [x, y];
31
+ }
32
+
33
+ handleMouseDown(event) {
34
+ const p = this.getCanvasPosition(event);
35
+
36
+ for (let i = this.layers.length - 1; i >= 0; i--) {
37
+ const layer = this.layers[i];
38
+ if (layer.accepts(p)) {
39
+ layer.mouseDown(p);
40
+ this.draggingLayer = layer;
41
+ this.dragStart = p;
42
+ break;
43
+ }
44
+ }
45
+ }
46
+
47
+ handleMouseMove(event) {
48
+ this.mouseCursor = this.getCanvasPosition(event);
49
+ if (this.draggingLayer) {
50
+ this.draggingLayer.mouseMove(this.getCanvasPosition(event)); // 念のため別の実体
51
+ }
52
+ this.render();
53
+ }
54
+
55
+ handleMouseUp(event) {
56
+ if (this.draggingLayer) {
57
+ this.draggingLayer.mouseUp(this.getCanvasPosition(event));
58
+ this.draggingLayer = null;
59
+ }
60
+ }
61
+
62
+ handleMouseLeave(event) {
63
+ this.mouseCursor = [-1,-1];
64
+ if (this.draggingLayer) {
65
+ this.handleMouseUp(event);
66
+ }
67
+ }
68
+
69
+ render() {
70
+ for (let i = 0; i < this.layers.length; i++) {
71
+ const layer = this.layers[i];
72
+ layer.render(this.canvas, this.context);
73
+ }
74
+ }
75
+
76
+ redraw() {
77
+ this.render();
78
+ }
79
+
80
+ addLayer(layer) {
81
+ this.layers.push(layer);
82
+ }
83
+
84
+ }
85
+
86
+
87
+ let mouseSequence = { // mixin
88
+ mouseDown(p) {
89
+ this.mouseHandler = this.mouse(p);
90
+ },
91
+ mouseMove(p) {
92
+ if (this.mouseHandler) {
93
+ this.mouseHandler.next(p);
94
+ }
95
+ },
96
+ mouseUp(p) {
97
+ if (this.mouseHandler) {
98
+ this.mouseHandler.next(null);
99
+ this.mouseHandler = null;
100
+ }
101
+ },
102
+ /*
103
+ sample mouse handler
104
+ *mouse(p) {
105
+ while (p = yield) {
106
+ console.log("mouse", p);
107
+ }
108
+ }
109
+ */
110
+ };
111
+
112
+ export function sequentializeMouse(layerClass) {
113
+ layerClass.mouseDown = mouseSequence.mouseDown;
114
+ layerClass.mouseMove = mouseSequence.mouseMove;
115
+ layerClass.mouseUp = mouseSequence.mouseUp;
116
+ }
117
+
118
+ export class Layer {
119
+ constructor() {}
120
+
121
+ reserveRender() { renderReserved = true; }
122
+
123
+ accepts(point) { return false; }
124
+ mouseDown(point) {}
125
+ mouseMove(point) {}
126
+ mouseUp(point) {}
127
+ render(canvas, ctx) {}
128
+ }
js/limb.js ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const limbSeq = [
2
+ [1, 2], [2, 3], [3, 4], // 右腕
3
+ [1, 5], [5, 6], [6, 7], // 左腕
4
+ [1, 8], [8, 9], [9, 10], // 右胴→右脚
5
+ [1, 11], [11, 12], [12, 13], // 左胴→左脚
6
+ [1, 0], // 首
7
+ [0, 14], [14, 16], // 右目
8
+ [0, 15], [15, 17] // 左目
9
+ ];
10
+
11
+ let parentNodeIndices = Array(18).fill(-1);
12
+ let childNodeIndices = Array(18).fill(-1);
13
+ let outEdgeIndices = Array(18).fill(-1);
14
+ let inEdgeIndices = Array(18).fill(-1);
15
+
16
+ function initializeLimb() {
17
+ limbSeq.forEach((limb, i) => {
18
+ parentNodeIndices[limb[1]] = limb[0];
19
+ childNodeIndices[limb[0]] = limb[1];
20
+ outEdgeIndices[limb[0]] = i;
21
+ inEdgeIndices[limb[1]] = i;
22
+ });
23
+ }
24
+
25
+ function findParentNodeIndex(nodeIndex) {
26
+ return parentNodeIndices[nodeIndex];
27
+ }
28
+
29
+ function findChildNodeIndex(nodeIndex) {
30
+ return childNodeIndices[nodeIndex];
31
+ }
32
+
33
+ function findOutEdgeIndex(nodeIndex) {
34
+ return outEdgeIndices[nodeIndex];
35
+ }
36
+
37
+ function findInEdgeIndex(nodeIndex) {
38
+ return inEdgeIndices[nodeIndex];
39
+ }
40
+
41
+ function findNextEdgeIndex(edgeIndex) {
42
+ return findOutEdgeIndex(limbSeq[edgeIndex][1]);
43
+ }
44
+
45
+ function findPrevEdgeIndex(edgeIndex) {
46
+ return findInEdgeIndex(limbSeq[edgeIndex][0]);
47
+ }
48
+
49
+ initializeLimb();
js/poseAnimator.js ADDED
@@ -0,0 +1,379 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const monitor = [];
2
+ function eachMonitor(p, f) {
3
+ monitor.forEach((m) => {
4
+ if (m[0] === p[0] && m[1] === p[1]) {
5
+ f(m);
6
+ }
7
+ });
8
+ }
9
+ function isMonitored(p) {
10
+ let result = false;
11
+ eachMonitor(p, (m) => {
12
+ result = true;
13
+ });
14
+ return result;
15
+ }
16
+
17
+
18
+ // 2D projection
19
+ function worldToLocal(pointA, pointB, pointC) {
20
+ const vectorAB = [pointB[0] - pointA[0], pointB[1] - pointA[1]];
21
+ const vectorAC = [pointC[0] - pointA[0], pointC[1] - pointA[1]];
22
+ const dot1 = dot2D(vectorAB, vectorAC);
23
+ const dot2 = dot2D(vectorAB, vectorAB);
24
+ const localX = dot1 / dot2;
25
+ const pointD = [pointA[0] + localX * vectorAB[0], pointA[1] + localX * vectorAB[1]];
26
+
27
+ // localY = distance from pointC to pointD
28
+ const vectorCD = [pointD[0] - pointC[0], pointD[1] - pointC[1]];
29
+ let localY = Math.sqrt(dot2D(vectorCD, vectorCD));
30
+
31
+ // if pointC is on the right side of vectorAB, localY is negative
32
+ const cross = cross2D(vectorAB, vectorAC);
33
+ if (0 < cross) {
34
+ localY = -localY;
35
+ }
36
+
37
+ return [localX, localY];
38
+ }
39
+
40
+ function distPointFromSeg(pointA, pointB, pointC, lx, ly) {
41
+ if (lx < 0) {
42
+ const vectorAC = [pointC[0] - pointA[0], pointC[1] - pointA[1]];
43
+ return Math.sqrt(dot2D(vectorAC, vectorAC));
44
+ }
45
+ if (1.0 < lx) {
46
+ const vectorBC = [pointC[0] - pointB[0], pointC[1] - pointB[1]];
47
+ return Math.sqrt(dot2D(vectorBC, vectorBC));
48
+ }
49
+ return Math.abs(ly);
50
+ }
51
+
52
+ function localToWorld([pointA, pointB], pointC) { // C is local coordinate
53
+ const vectorAB = [pointB[0] - pointA[0], pointB[1] - pointA[1]];
54
+ // pointD = pointA + C.x * vectorAB(垂線との交点)
55
+ const pointD = [pointA[0] + pointC[0] * vectorAB[0], pointA[1] + pointC[0] * vectorAB[1]];
56
+ const v = perpendicular2D(vectorAB);
57
+ const vLength = Math.sqrt(dot2D(v, v));
58
+ if (vLength < 0.0001) { return pointD; }
59
+
60
+ const newLength = pointC[1];
61
+ const factor = newLength / vLength;
62
+ return [pointD[0] + factor * v[0], pointD[1] + factor * v[1]];
63
+ }
64
+
65
+ function makeEdgeSegments(pose) {
66
+ return limbSeq.map((segment) => {
67
+ const p0 = pose[segment[0]];
68
+ const p1 = pose[segment[1]];
69
+ return [p0, p1];
70
+ });
71
+ }
72
+
73
+ function makeNormalizeEdgeSegments(edges) {
74
+ return edges.map((edge) => normalize2D([edge[1][0] - edge[0][0], edge[1][1] - edge[0][1]]));
75
+ }
76
+
77
+ const fieldDepth = 5;
78
+ const FO0i = 0;
79
+ const FO0t = 1;
80
+ const FO0u = 2;
81
+ const FO0d = 3;
82
+
83
+ // c current
84
+ // p prev
85
+ // n next
86
+ // 0 primary
87
+ // 1 secondary
88
+ // e edge
89
+ // s segment
90
+ // i in
91
+ // o out
92
+ // t time
93
+ // l local
94
+ // q point
95
+ // m main
96
+ // d distance
97
+ // w weight
98
+
99
+ function calcJointRange(
100
+ edgeSegments, normalizedEdgeSegments,
101
+ edgeSegmentsForSideDecision,
102
+ ep, ec, m) {
103
+ // parent, child
104
+ const np = normalizedEdgeSegments[ep];
105
+ const nc = normalizedEdgeSegments[ec];
106
+ let pnp = perpendicular2D(np);
107
+ let pnc = perpendicular2D(nc);
108
+ let side = getSide(edgeSegmentsForSideDecision[ep][0], edgeSegmentsForSideDecision[ep][1], edgeSegmentsForSideDecision[ec][1], m);
109
+ if (side < 0) {
110
+ pnp = reverse2D(pnp);
111
+ pnc = reverse2D(pnc);
112
+ }
113
+ return [pnp, pnc];
114
+ }
115
+
116
+ function calcJointRangeWithOrder(oldEdgeSegments, oldNormalizedEdgeSegments, newEdgeSegments, e0, m, order) {
117
+ if (order < 0) {
118
+ const e1 = findPrevEdgeIndex(e0);
119
+ if (e1 < 0) { return null; }
120
+ const [pn1, pn0] = calcJointRange(oldEdgeSegments, oldNormalizedEdgeSegments, newEdgeSegments,e1, e0, m);
121
+ const oldPivot = oldEdgeSegments[e0][0];
122
+ const newPivot = newEdgeSegments[e0][0];
123
+ return [pn0, pn1, oldPivot, newPivot, e1];
124
+ } else if (0 < order) {
125
+ const e1 = findNextEdgeIndex(e0);
126
+ if (e1 < 0) { return null; }
127
+ const [pn0, pn1] = calcJointRange(oldEdgeSegments, oldNormalizedEdgeSegments, newEdgeSegments, e0, e1, m);
128
+ const oldPivot = oldEdgeSegments[e0][1];
129
+ const newPivot = newEdgeSegments[e0][1];
130
+ return [pn0, pn1, oldPivot, newPivot, e1];
131
+ } else {
132
+ return null;
133
+ }
134
+ }
135
+
136
+ function buildWeightMap(size, pose) {
137
+ const threshold = 512;
138
+
139
+ const [w, h] = size;
140
+
141
+ // この状態で、canvasに対して、
142
+ // 同じ大きさの3次元配列[x,y,z]を作成する
143
+ // x,yは画像の2次元座標
144
+
145
+ // 各Pixelに対して、
146
+ // そのPixelに最も近いエッジのindexを[0]に、
147
+ // そのときのエッジ座標系におけるtを[1]に記録する
148
+ // どの骨格とも近くないものは[0]=255とする
149
+
150
+ const field = new Float32Array(w * h * fieldDepth)
151
+ function fieldIndex(x, y) {
152
+ return (y * w + x) * fieldDepth;
153
+ }
154
+
155
+ const edgeSegments = makeEdgeSegments(pose);
156
+ const normalizedEdgeSegments = makeNormalizeEdgeSegments(edgeSegments);
157
+
158
+ // 各Pixelに対して、一番近いエッジを探す
159
+ // またそのエッジ座標系におけるtを保存する
160
+ for (let y = 0 ; y < h ; y++) {
161
+ for (let x = 0 ; x < w ; x++) {
162
+ const m = [x, y];
163
+ let minDist = threshold;
164
+ const fidx = fieldIndex(x, y);
165
+ field[fidx+FO0i] = -1;
166
+ for (let i = 0 ; i < limbSeq.length ; i++) {
167
+ // 線分から���への距離を計算
168
+ const s = edgeSegments[i];
169
+ const lq = worldToLocal(s[0], s[1], m);
170
+ const dist = distPointFromSeg(s[0], s[1], m, lq[0], lq[1]);
171
+ if (dist < minDist) {
172
+ minDist = dist;
173
+ field[fidx+FO0i] = i;
174
+ field[fidx+FO0t] = lq[0];
175
+ field[fidx+FO0u] = lq[1];
176
+ field[fidx+FO0d] = dist;
177
+ }
178
+ }
179
+ }
180
+ }
181
+
182
+ return field;
183
+ }
184
+
185
+ function renderField(canvas2, size, field) {
186
+ function blend(c0, w0, c1, w1) {
187
+ const r = (c0[0] * w0 + c1[0] * w1);
188
+ const g = (c0[1] * w0 + c1[1] * w1);
189
+ const b = (c0[2] * w0 + c1[2] * w1);
190
+ const a = (c0[3] * w0 + c1[3] * w1);
191
+ return [r, g, b, a];
192
+ }
193
+
194
+ const [w, h] = size;
195
+ function fieldIndex(x, y) {
196
+ return (y * w + x) * fieldDepth;
197
+ }
198
+
199
+ canvas2.width = w;
200
+ canvas2.height = h;
201
+ const ctx2 = canvas2.getContext('2d');
202
+
203
+ bindings = ctx2.createImageData(w, h);
204
+ const data = bindings.data;
205
+ for (let y = 0 ; y < h ; y++) {
206
+ for (let x = 0 ; x < w ; x++) {
207
+ const fidx = fieldIndex(x, y);
208
+ const oi = (y * w + x) * 4;
209
+
210
+ let c = [0, 0, 0, 64];
211
+
212
+ const e0 = field[fidx+FO0i];
213
+ if (0 <= e0) {
214
+ c = colors[e0];
215
+ c[3] = 255;
216
+ // c[3] = 255 - field[fidx+FO0d] * 4;
217
+ }
218
+ data[oi + 0] = c[0];
219
+ data[oi + 1] = c[1];
220
+ data[oi + 2] = c[2];
221
+ data[oi + 3] = c[3];
222
+ }
223
+ }
224
+ ctx2.putImageData(bindings, 0, 0);
225
+
226
+ return field;
227
+ }
228
+
229
+ function makeImageDataFromPicture(canvas3, size, picture) {
230
+ const [w, h] = size;
231
+ canvas3.width = w;
232
+ canvas3.height = h;
233
+
234
+ const ctx3 = canvas3.getContext("2d");
235
+ // ctx3.drawImage(picture, sampleOffset[0], sampleOffset[1]);
236
+ ctx3.drawImage(picture, 0, 0);
237
+
238
+ return new ImageData(new Uint8ClampedArray(ctx3.getImageData(0, 0, w, h).data), w, h);
239
+ }
240
+
241
+ function animatePicture(canvas4, size, oldPose, newPose, srcImageData, initialPoseField) {
242
+ const srcData = srcImageData.data;
243
+
244
+ const [w, h] = size;
245
+
246
+ const ctx4 = canvas4.getContext("2d");
247
+ canvas4.width = w;
248
+ canvas4.height = h;
249
+ const dstCtx = ctx4;
250
+ const dstImageData = dstCtx.createImageData(w, h);
251
+ const dstData = dstImageData.data;
252
+
253
+ const field = buildWeightMap(size, newPose);
254
+ const canvas2 = document.getElementById("canvas2");
255
+ // renderField(canvas2, size, field);
256
+ drawBodyPoseTo(canvas2.getContext("2d"), [newPose]);
257
+
258
+ function fieldIndex(x, y) {
259
+ return (y * w + x) * fieldDepth;
260
+ }
261
+
262
+ const oldEdgeSegments = makeEdgeSegments(oldPose);
263
+ const oldNormalizedEdgeSegments = makeNormalizeEdgeSegments(oldEdgeSegments);
264
+ const newEdgeSegments = makeEdgeSegments(newPose);
265
+
266
+ function oldEdgePoint(edgeIndex, p) {
267
+ const v = oldEdgeSegments[edgeIndex];
268
+ return localToWorld(v, p);
269
+ }
270
+
271
+ function newEdgePoint(edgeIndex, p) {
272
+ const v = newEdgeSegments[edgeIndex];
273
+ return localToWorld(v, p);
274
+ }
275
+
276
+ debugLines = [];
277
+
278
+ for (let y = 0 ; y < h ; y++) {
279
+ for (let x = 0 ; x < w ; x++) {
280
+ // 各点に対して、fieldから近隣エッジ座標系でのローカル座標を取得する
281
+ const oi = (y * w + x) * 4;
282
+
283
+ const fidx = fieldIndex(x, y);
284
+ const e0 = field[fidx+FO0i];
285
+ if (e0 < 0) {
286
+ dstData[oi + 0] = 0;
287
+ dstData[oi + 1] = 0;
288
+ dstData[oi + 2] = 0;
289
+ dstData[oi + 3] = 255;
290
+ continue;
291
+ }
292
+
293
+ const lq = [field[fidx+FO0t], field[fidx+FO0u]];
294
+ let e1 = -1;
295
+ let order = 0; // 1 == forward, -1 == backward
296
+ if (lq[0] < 0.5) {
297
+ e1 = findPrevEdgeIndex(e0);
298
+ } else if (0.5 <= lq[0]) {
299
+ e1 = findNextEdgeIndex(e0);
300
+ }
301
+ if (0 <= e1) {
302
+ if (lq[0] < 0) {
303
+ order = -1;
304
+ } else if (1 < lq[0]) {
305
+ order = 1;
306
+ }
307
+ }
308
+
309
+ let iq = oldEdgePoint(e0, lq);
310
+
311
+ if (0 != order) {
312
+ // サイド判定は新ポーズで行う
313
+ const mNew = [x,y];
314
+ const jointRange = calcJointRangeWithOrder(
315
+ oldEdgeSegments, oldNormalizedEdgeSegments,
316
+ newEdgeSegments,
317
+ e0, mNew, order);
318
+ const [pn0, pn1, oldPivot, newPivot, _] = jointRange;
319
+
320
+ const pivotMNew = [x - newPivot[0], y - newPivot[1]];
321
+ const [w0, w1]= [angleBetween(pn0, pivotMNew), angleBetween(pivotMNew, pn1)];
322
+
323
+ let d0 = field[fidx+FO0d];
324
+ let mt = slerp2D(pn0, pn1, w0 / (w0 + w1));
325
+ iq = [oldPivot[0] + mt[0] * d0, oldPivot[1] + mt[1] * d0];
326
+ eachMonitor([x,y], () => {
327
+ console.log('animate', x, y, ix, iy, e1, w0, w1, pn0, pn1);
328
+ debugLines.push([oldPivot, pn0]);
329
+ debugLines.push([oldPivot, pn1]);
330
+ });
331
+ }
332
+
333
+ iq = [Math.round(iq[0]), Math.round(iq[1])];
334
+
335
+ var initialOwner = initialPoseField[fieldIndex(iq[0], iq[1]) + FO0i];
336
+ if (0 <= initialOwner && initialOwner != e0 && initialOwner != e1) {
337
+ // ほかの領土
338
+ dstData[oi + 0] = 0;
339
+ dstData[oi + 1] = 0;
340
+ dstData[oi + 2] = 0;
341
+ dstData[oi + 3] = 0;
342
+ } else {
343
+ // canvas3の[ix,iy]の色を取得して、canvas4の[x,y]に描画する
344
+ const si = (iq[1] * w + iq[0]) * 4;
345
+ dstData[oi + 0] = srcData[si + 0];
346
+ dstData[oi + 1] = srcData[si + 1];
347
+ dstData[oi + 2] = srcData[si + 2];
348
+ dstData[oi + 3] = srcData[si + 3];
349
+ }
350
+ }
351
+ }
352
+
353
+ dstCtx.putImageData(dstImageData, 0, 0);
354
+ }
355
+
356
+ function handleMicroscopeMouseMove(e) {
357
+ const [x,y] = mouseCursor;
358
+
359
+ const imageData = ctx.getImageData(x - 2, y - 2, 5, 5);
360
+ const data = imageData.data;
361
+
362
+ const microscope = document.getElementById('microscope');
363
+ const ctx2 = microscope.getContext('2d');
364
+ ctx2.clearRect(0, 0, microscope.width, microscope.height);
365
+ for (let i = 0 ; i < 5 ; i++) {
366
+ for (let j = 0 ; j < 5 ; j++) {
367
+ const idx = (i * 5 + j) * 4;
368
+ ctx2.fillStyle = `rgba(${data[idx]}, ${data[idx+1]}, ${data[idx+2]}, ${data[idx+3]})`;
369
+ ctx2.fillRect(j * 10, i * 10, 10, 10);
370
+ }
371
+ }
372
+ }
373
+
374
+ function initMicroscope() {
375
+ // canvas.addEventListener('mousemove', handleMicroscopeMouseMove);
376
+ // poseData[0][6] = [329, 148];
377
+ // poseData[0][7] = [329, 194];
378
+ }
379
+
js/poseTweek.js ADDED
@@ -0,0 +1,862 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ var canvas = null;
2
+ var ctx = null;
3
+ var offscreen = null;
4
+ var initialPoseField = null;
5
+ var canvasBg = null;
6
+ var debugLines = [];
7
+
8
+ const wheelDisplayTime = 500;
9
+
10
+ const colors = [[255, 0, 0], [255, 85, 0], [255, 170, 0], [255, 255, 0], [170, 255, 0], [85, 255, 0], [0, 255, 0],
11
+ [0, 255, 85], [0, 255, 170], [0, 255, 255], [0, 170, 255], [0, 85, 255], [0, 0, 255], [85, 0, 255],
12
+ [170, 0, 255], [255, 0, 255], [255, 0, 170], [255, 0, 85]];
13
+
14
+ function cutOffLimb(pose, cutOffIndex) {
15
+ console.log(`cutOffLimb: ${cutOffIndex}`);
16
+ // 末端ノードの座標を削除する
17
+ var newPose = deepCopy(pose);
18
+ for (let i = 0; i < 18; i++) {
19
+ if (newPose[i] == null) {continue;}
20
+ // ルートまで検索し、その間にcuttOffIndexがあれば削除
21
+ var curr = i;
22
+ while (curr !== 1) {
23
+ console.log(`checking: ${i} -> ${curr}`);
24
+ let parent = findParentNodeIndex(curr);
25
+ if (parent === cutOffIndex) {
26
+ console.log(`cutOffLimb: ${i} -> ${cutOffIndex}`);
27
+ newPose[i] = null;
28
+ break;
29
+ }
30
+ curr = parent;
31
+ }
32
+ }
33
+ return newPose;
34
+ }
35
+
36
+ function repairPose(sourcePose) {
37
+ // TODO: ループには対応してないかも
38
+ var pose = sourcePose;
39
+ var newPose = new Array(18)
40
+ for (var k = 0; k < 3; k++) {
41
+ var processed = 0; // イテレーション用
42
+ for (let i = 0; i < 18; i++) {
43
+ if (pose[i] == null) {
44
+ let parent = findParentNodeIndex(i);
45
+ if (parent === -1) {continue;} // あり得ない
46
+ if (pose[parent] == null) {
47
+ console.log(`repair failed(A): ${i} -> parent loss`);
48
+ continue;
49
+ }
50
+
51
+ // サンプルデータから引っ張ってくる
52
+ var v = sampleCandidateSource[i].map((x, j) => x - sampleCandidateSource[parent][j]);
53
+ newPose[i] = pose[parent].map((x, j) => x + v[j]);
54
+ console.log(`repaired: ${i} -> ${newPose[newPose.length - 1]}`);
55
+ processed++;
56
+ } else {
57
+ newPose[i] = pose[i].map(x => x);
58
+ }
59
+ }
60
+ if (processed === 0) {break;}
61
+ pose = newPose;
62
+ }
63
+ return newPose;
64
+ }
65
+
66
+ function deepCopy(arr) {
67
+ return JSON.parse(JSON.stringify(arr));
68
+ }
69
+
70
+ function distSq(p0, p1) {
71
+ return (p0[0] - p1[0]) ** 2 + (p0[1] - p1[1]) ** 2;
72
+ }
73
+
74
+ // poseDataの形式:[[[x1, y1], [x2, y2], ...],[[x3, y3], [x4, y4], ...], ...]
75
+ // 各要素が人間
76
+ // 人間の各要素が関節
77
+
78
+ function poseDataToCandidateAndSubset(poseData) {
79
+ let candidate = [];
80
+ let subset = [];
81
+ for (let i = 0; i < poseData.length; i++) {
82
+ let person = poseData[i];
83
+ let subsetElement = [];
84
+ for (let j = 0; j < person.length; j++) {
85
+ candidate.push(person[j]);
86
+ subsetElement.push(candidate.length - 1);
87
+ }
88
+ subset.push(subsetElement);
89
+ }
90
+ return [candidate, subset];
91
+ }
92
+
93
+ // サンプルデータ
94
+ const sampleOffset = [0, -70];
95
+ const sampleCandidateSource = [[235, 158],[234, 220],[193, 222],[138, 263],[89, 308],[276, 220],[325, 264],[375, 309],[207, 347],[203, 433],[199, 523],[261, 347],[262, 430],[261, 522],[227, 148],[245, 148],[208, 158],[258, 154]].map((p) => [p[0] + sampleOffset[0], p[1] + sampleOffset[1]]);
96
+ const sampleSubsetElementSource = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17];
97
+
98
+ // const sampleCandidateSource = [[618.00, 0.00], [618.00, 44.00], [304.00, 81.00], [482.00, 96.00], [66.00, 270.00], [171.00, 280.00], [618.00, 82.00], [307.00, 112.00], [460.00, 143.00], [0.00, 301.00], [65.00, 301.00], [172.00, 303.00], [584.00, 86.00], [275.00, 119.00], [420.00, 139.00], [0.00, 301.00], [41.00, 301.00], [144.00, 303.00], [544.00, 131.00], [348.00, 139.00], [262.00, 160.00], [0.00, 337.00], [52.00, 339.00], [130.00, 348.00], [570.00, 175.00], [283.00, 177.00], [78.00, 338.00], [172.00, 380.00], [651.00, 78.00], [338.00, 111.00], [505.00, 144.00], [92.00, 301.00], [198.00, 305.00], [661.00, 132.00], [349.00, 156.00], [541.00, 179.00], [106.00, 336.00], [203.00, 348.00], [305.00, 159.00], [665.00, 160.00], [563.00, 192.00], [80.00, 343.00], [181.00, 385.00], [614.00, 205.00], [291.00, 220.00], [432.00, 320.00], [152.00, 372.00], [43.00, 380.00], [0.00, 386.00], [623.00, 281.00], [306.00, 290.00], [92.00, 357.00], [509.00, 434.00], [304.00, 357.00], [622.00, 368.00], [47.00, 394.00], [0.00, 395.00], [142.00, 405.00], [535.00, 565.00], [655.00, 200.00], [337.00, 217.00], [467.00, 322.00], [191.00, 372.00], [83.00, 375.00], [344.00, 282.00], [655.00, 282.00], [103.00, 343.00], [237.00, 368.00], [22.00, 377.00], [0.00, 379.00], [460.00, 459.00], [305.00, 352.00], [638.00, 355.00], [0.00, 401.00], [110.00, 412.00], [411.00, 570.00], [608.00, 0.00], [608.00, 40.00], [297.00, 75.00], [469.00, 84.00], [0.00, 261.00], [58.00, 263.00], [165.00, 275.00], [625.00, 0.00], [625.00, 39.00], [309.00, 74.00], [486.00, 83.00], [71.00, 264.00], [180.00, 276.00], [599.00, 0.00], [599.00, 44.00], [284.00, 80.00], [440.00, 93.00], [48.00, 271.00], [0.00, 272.00], [157.00, 277.00], [634.00, 0.00], [633.00, 41.00], [319.00, 77.00], [79.00, 269.00], [190.00, 277.00]];
99
+ // const sampleSubsetElementSource = [1.00,6.00,12.00,18.00,24.00,28.00,33.00,39.00,43.00,49.00,54.00,59.00,65.00,72.00,77.00,84.00,90.00,97.00,32.98,18.00],[5.00,11.00,17.00,23.00,27.00,32.00,37.00,42.00,46.00,-1.00,-1.00,62.00,67.00,-1.00,82.00,88.00,95.00,100.00,25.45,15.00],[4.00,10.00,16.00,22.00,26.00,31.00,36.00,41.00,47.00,51.00,57.00,63.00,66.00,74.00,81.00,87.00,93.00,99.00,26.97,18.00],[3.00,8.00,14.00,19.00,25.00,30.00,35.00,40.00,45.00,52.00,58.00,61.00,70.00,75.00,79.00,86.00,92.00,-1.00,30.45,17.00],[2.00,7.00,13.00,20.00,-1.00,29.00,34.00,38.00,44.00,50.00,53.00,60.00,64.00,71.00,78.00,85.00,91.00,98.00,27.89,17.00],[0.00,-1.00,-1.00,-1.00,-1.00,-1.00,-1.00,-1.00,-1.00,-1.00,-1.00,-1.00,-1.00,-1.00,76.00,83.00,-1.00,96.00,3.33,4.00];
100
+
101
+ function makePoseFromCandidateAndSubsetElement(candidate, subsetElement) {
102
+ var pose = [];
103
+ for (let j = 0 ; j < 18; j++) {
104
+ let i = subsetElement[j];
105
+ pose.push(i < 0 || candidate[i] == null ? null : candidate[i].map((x)=>x));
106
+ }
107
+ return pose;
108
+ }
109
+
110
+ function makePoseDataFromCandidateAndSubset(candidate, subset) {
111
+ return subset.map(subsetElement => makePoseFromCandidateAndSubsetElement(candidate, subsetElement));
112
+ }
113
+
114
+ function addPerson() {
115
+ var dx = Math.random() * 100;
116
+ var dy = Math.random() * 100;
117
+
118
+ poseData.push(
119
+ makePoseFromCandidateAndSubsetElement(
120
+ sampleCandidateSource.map(point => [point[0] + dx, point[1] + dy]),
121
+ sampleSubsetElementSource));
122
+
123
+ addHistory();
124
+ Redraw();
125
+ }
126
+
127
+ function removePerson(personIndex) {
128
+ poseData.splice(personIndex, 1);
129
+ addHistory();
130
+ Redraw();
131
+ }
132
+
133
+ function repairPerson(personIndex) {
134
+ poseData[personIndex] = repairPose(poseData[personIndex]);
135
+ addHistory();
136
+ Redraw();
137
+ }
138
+
139
+ function cutOffPersonLimb(personIndex, limbIndex) {
140
+ poseData[personIndex] = cutOffLimb(poseData[personIndex], limbIndex);
141
+ console.log(poseData[personIndex]);
142
+ console.log(poseData);
143
+ addHistory();
144
+ Redraw();
145
+ }
146
+
147
+ // ドラッグ中の各キーが押されているかどうかのフラグ
148
+ var keyDownFlags = {};
149
+ // マウスカーソル
150
+ var mouseCursor = [-1, -1];
151
+
152
+ function cross(lhs, rhs) {return lhs[0] * rhs[1] - lhs[1] * rhs[0];}
153
+ function dot(lhs, rhs) {return lhs[0] * rhs[0] + lhs[1] * rhs[1];}
154
+ function directedAngleTo(lhs, rhs) {return Math.atan2(cross(lhs, rhs), dot(lhs, rhs));}
155
+
156
+ function isMouseOnCanvas() {
157
+ // mouseCursorがcanvasの範囲内にあるかどうかを判定
158
+ var rect = canvas.getBoundingClientRect();
159
+ var f = 0 <= mouseCursor[0] && mouseCursor[0] <= rect.width && 0 <= mouseCursor[1] && mouseCursor[1] <= rect.height;
160
+ return f;
161
+ }
162
+
163
+ function clearCanvas() {
164
+ var w = canvas.width;
165
+ var h = canvas.height;
166
+ ctx.fillStyle = 'black';
167
+ ctx.fillRect(0, 0, w, h);
168
+ }
169
+
170
+ function drawBackground() {
171
+ if (canvasBg != null) {
172
+ ctx.drawImage(canvasBg, 0, 0);
173
+ }
174
+ }
175
+
176
+ function resizeCanvas(width, height) {
177
+ canvas.width = width ? width : canvas.width;
178
+ canvas.height = height ? height : canvas.height;
179
+ Redraw();
180
+ }
181
+
182
+ function calculateCenter(shape) {
183
+ var center = shape.reduce(function(acc, point) {
184
+ if (point === null) {
185
+ acc[0] += point[0];
186
+ acc[1] += point[1];
187
+ }
188
+ return acc;
189
+ }, [0, 0]);
190
+ center[0] /= shape.length;
191
+ center[1] /= shape.length;
192
+ return center;
193
+ }
194
+
195
+ // v2d -> v3d
196
+ function rotateX(vector, angle) {
197
+ var x = vector[0];
198
+ var y = vector[1];
199
+ var z = 0;
200
+
201
+ // X軸に対して回転する
202
+ var x1 = x;
203
+ var y1 = y * Math.cos(angle) - z * Math.sin(angle);
204
+ var z1 = y * Math.sin(angle) + z * Math.cos(angle);
205
+
206
+ return [x1, y1, z1];
207
+ }
208
+
209
+ // v2d -> v3d
210
+ function rotateY(vector, angle) {
211
+ var x = vector[0];
212
+ var y = vector[1];
213
+ var z = 0;
214
+
215
+ // Y軸に対して回転する
216
+ var x1 = x * Math.cos(angle) + z * Math.sin(angle);
217
+ var y1 = y;
218
+ var z1 = -x * Math.sin(angle) + z * Math.cos(angle);
219
+
220
+ return [x1, y1, z1];
221
+ }
222
+
223
+ // v3d -> v2d
224
+ function perspectiveProjection(vector, cameraDistance) {
225
+ var x = vector[0];
226
+ var y = vector[1];
227
+ var z = vector[2];
228
+
229
+ if (z === 0) {
230
+ return [x, y];
231
+ }
232
+
233
+ var scale = cameraDistance / (cameraDistance - z);
234
+ var x1 = x * scale;
235
+ var y1 = y * scale;
236
+
237
+ return [x1, y1];
238
+ }
239
+
240
+ // v2d -> v3d
241
+ function rotateAndProject(f, p, c, angle) {
242
+ var v = [p[0] - c[0], p[1] - c[1]];
243
+ var v1 = f(v, angle);
244
+ var v2 = perspectiveProjection(v1, 500);
245
+ return [v2[0] + c[0], v2[1] + c[1]];
246
+ }
247
+
248
+ function drawPicture() {
249
+ ctx.globalAlpha = 1.0;
250
+ // if (picture != null) {
251
+ //var [w, h] = [canvas.width, canvas.height];
252
+ //var [pw, ph] = [picture.width, picture.height];
253
+ //var [x, y] = [(w-pw)/2, (h-ph)/2];
254
+ // ctx.drawImage(picture, sampleOffset[0], sampleOffset[1]);
255
+ // ctx.drawImage(picture, 0, 0);
256
+ // }
257
+
258
+ ctx.globalAlpha = 1.0;
259
+ if (offscreen != null) {
260
+ ctx.drawImage(offscreen, 0, 0);
261
+ }
262
+ }
263
+
264
+ function drawBodyPose() {
265
+ drawBodyPoseTo(ctx, poseData);
266
+ }
267
+
268
+ function drawBodyPoseTo(ctx, poseData) {
269
+ let stickWidth = 4;
270
+ let imageSize = Math.min(canvas.width, canvas.height);
271
+ stickWidth *= imageSize / 512;
272
+
273
+ ctx.globalAlpha = 0.6;
274
+
275
+ // edge
276
+ for (let i = 0; i < poseData.length; i++) {
277
+ const pose = poseData[i];
278
+
279
+ for (let j = 0; j < 17; j++) {
280
+ const p = pose[limbSeq[j][0]];
281
+ const q = pose[limbSeq[j][1]];
282
+ if (p == null || q == null) continue;
283
+ const [X0, Y0] = p;
284
+ const [X1, Y1] = q;
285
+ let angle = Math.atan2(Y1 - Y0, X1 - X0);
286
+ let magnitude = ((X0 - X1) ** 2 + (Y0 - Y1) ** 2) ** 0.5
287
+ let polygon = new Path2D();
288
+ polygon.ellipse((X0+X1)/2, (Y0+Y1)/2, magnitude / 2, stickWidth, angle, 0, 2 * Math.PI);
289
+ //ctx.fillStyle = `rgb(${colors[j].join(',')})`;
290
+ ctx.fillStyle = 'gray';
291
+ ctx.fill(polygon);
292
+ }
293
+ }
294
+
295
+ ctx.globalAlpha = 1.0;
296
+
297
+ // node
298
+ for (let i = 0; i < poseData.length; i++) {
299
+ const pose = poseData[i];
300
+
301
+ ctx.font = '12px serif';
302
+ for (let j = 0; j < 18; j++) {
303
+ const p = pose[j];
304
+ if (p == null) continue;
305
+ const [x, y] = p;
306
+ ctx.beginPath();
307
+ ctx.arc(x, y, stickWidth, 0, 2 * Math.PI);
308
+ ctx.fillStyle = `rgb(${colors[j].join(',')})`;
309
+ ctx.fill();
310
+ // ctx.fillStyle = 'rgb(255,255,255)'
311
+ // ctx.fillText(j, x-3, y+4);
312
+ }
313
+ }
314
+ }
315
+
316
+ let lastWheeling = 0;
317
+
318
+ function drawUI() {
319
+ if (keyDownFlags['Space'] || keyDownFlags['BracketLeft'] || keyDownFlags['BracketRight'] ||
320
+ new Date().getTime() - lastWheeling < wheelDisplayTime) {
321
+ ctx.beginPath();
322
+ ctx.lineWidth=4;
323
+ ctx.arc(mouseCursor[0], mouseCursor[1], dragRange, 0, 2 * Math.PI);
324
+ ctx.strokeStyle = 'rgb(255,255,255)';
325
+ ctx.stroke();
326
+ }
327
+
328
+ if (isDragging && (dragMode == "rotate" || dragMode == "rotate2")) {
329
+ ctx.beginPath();
330
+ ctx.lineWidth=1;
331
+ ctx.strokeStyle = 'rgb(255,255,255)';
332
+ ctx.moveTo(dragStart[0], dragStart[1]);
333
+ ctx.lineTo(dragStart[0]+rotateBaseVector[0], dragStart[1]+rotateBaseVector[1]);
334
+ ctx.stroke();
335
+ }
336
+
337
+ ctx.globalAlpha = 0.1;
338
+
339
+ for (let i = 0 ; i < debugLines.length ; i++) {
340
+ const p = debugLines[i];
341
+ const l = 50;
342
+ ctx.beginPath();
343
+ ctx.lineWidth= 2;
344
+ ctx.strokeStyle = `rgb(${colors[i].join(',')})`;
345
+ ctx.moveTo(p[0][0], p[0][1]);
346
+ ctx.lineTo(p[0][0]+p[1][0] * l, p[0][1]+p[1][1] * l);
347
+ ctx.stroke();
348
+ }
349
+ ctx.globalAlpha = 1.0;
350
+
351
+ let operationTextFlags = {
352
+ "Space": "Range Move",
353
+ "AltLeft": "Body Move",
354
+ "AltRight": "Body Move",
355
+ "ControlLeft": "Scale",
356
+ "ControlRight": "Scale",
357
+ "ShiftLeft": "Rotate",
358
+ "ShiftRight": "Rotate",
359
+ "KeyQ": "CutOff",
360
+ "KeyD": "Delete",
361
+ "KeyX": "X-Axis",
362
+ "KeyC": "Y-Axis",
363
+ "KeyR": "Repair",
364
+ }
365
+
366
+ // operationTextFlagsに含まれるものがkeyDownFlagsに含まれるばあい、そのキーの文字列を取得
367
+ let activeOperations = Object.keys(operationTextFlags).filter(key => keyDownFlags[key]);
368
+ if (activeOperations.length > 0) {
369
+ // 左上に表示
370
+ ctx.font = '20px serif';
371
+ ctx.fillStyle = 'rgb(255,255,255)';
372
+ ctx.fillText(operationTextFlags[activeOperations[0]], 10, 30);
373
+ }
374
+
375
+ // draw mouse cursor position at right bottom
376
+ ctx.font = '20px serif';
377
+ ctx.fillStyle = 'rgb(255,255,255)';
378
+ ctx.fillText(`(${mouseCursor[0]}, ${mouseCursor[1]})`, canvas.width-200, canvas.height-10);
379
+
380
+ }
381
+
382
+ function Redraw() {
383
+ clearCanvas();
384
+ drawBackground();
385
+ drawPicture();
386
+ drawBodyPose();
387
+ drawUI();
388
+ }
389
+
390
+ function getNearestNode(p) {
391
+ let minDistSq = Infinity;
392
+ let personIndex = -1;
393
+ let nodeIndex = -1;
394
+ for (let i = 0; i < poseData.length; i++) {
395
+ const pose = poseData[i];
396
+ for (let j = 0; j < pose.length; j++) {
397
+ const q = pose[j];
398
+ if (q == null) continue;
399
+ const d = distSq(p, q);
400
+ if (d < minDistSq) {
401
+ minDistSq = d;
402
+ personIndex = i;
403
+ nodeIndex = j;
404
+ }
405
+ }
406
+ }
407
+ return [personIndex, nodeIndex, Math.sqrt(minDistSq)];
408
+ }
409
+
410
+ let dragRange = 64;
411
+ let dragRangeDelta = 16;
412
+
413
+ // ドラッグ中に座標を保持するための変数
414
+ let isDragging = false;
415
+ let dragStart = [0, 0];
416
+ let dragPersonIndex = -1;
417
+ let dragMarks = [];
418
+ let dragMode = "";
419
+ let rotateBaseVector = null;
420
+ let history = [];
421
+ let historyIndex = 0;
422
+
423
+ function clearHistory() {
424
+ history = [];
425
+ historyIndex = 0;
426
+ }
427
+
428
+ function addHistory() {
429
+ history = history.slice(0, historyIndex);
430
+ history.push(JSON.parse(JSON.stringify(poseData)));
431
+ historyIndex = history.length;
432
+ }
433
+
434
+ function undo() {
435
+ if (1 < historyIndex) {
436
+ historyIndex--;
437
+ poseData = deepCopy(history[historyIndex-1]);
438
+ Redraw();
439
+ }
440
+ }
441
+
442
+ function redo() {
443
+ if (historyIndex < history.length) {
444
+ historyIndex++;
445
+ poseData = deepCopy(history[historyIndex-1]);
446
+ Redraw();
447
+ }
448
+ }
449
+
450
+ function fetchLatestPoseData() {
451
+ return history[historyIndex-1];
452
+ }
453
+
454
+ function getCanvasPosition(event) {
455
+ const rect = canvas.getBoundingClientRect();
456
+ const x = Math.floor(event.clientX - rect.left);
457
+ const y = Math.floor(event.clientY - rect.top);
458
+ return [x, y];
459
+ }
460
+
461
+ function forEachMarkedNodes(fn) {
462
+ for (let i = 0; i < dragMarks.length; i++) {
463
+ for (let j = 0; j < dragMarks[i].length; j++) {
464
+ if (dragMarks[i][j]) {
465
+ fn(i, j, poseData[i][j]);
466
+ }
467
+ }
468
+ }
469
+ }
470
+
471
+ // Canvas要素上でマウスが押された場合に呼び出される関数
472
+ function handleMouseDown(event) {
473
+ const p = getCanvasPosition(event);
474
+ const [personIndex, nodeIndex, minDist] = getNearestNode(p);
475
+
476
+ if (keyDownFlags["KeyD"]) {removePerson(personIndex);return;}
477
+ if (keyDownFlags["KeyR"]) {repairPerson(personIndex);return;}
478
+
479
+ if (keyDownFlags["KeyQ"] && minDist < 16) {
480
+ console.log("pressed KeyQ");
481
+ cutOffPersonLimb(personIndex, nodeIndex);
482
+ return;
483
+ }
484
+
485
+ // ドラッグ処理の開始
486
+ dragStart = p;
487
+ dragMarks = poseData.map(pose => pose.map(node => false));
488
+
489
+ if (event.altKey || event.ctrlKey || event.shiftKey ||
490
+ keyDownFlags["KeyX"] || keyDownFlags["KeyC"]) {
491
+ // dragMarksを設定
492
+ dragMarks[personIndex] =
493
+ poseData[personIndex].map((node) => node != null);
494
+ isDragging = true;
495
+ if (event.altKey) {
496
+ dragMode = "move";
497
+ } else if (event.ctrlKey) {
498
+ dragMode = "scale";
499
+ } else if (event.shiftKey) {
500
+ dragMode = "rotate";
501
+ rotateBaseVector = [0, 0];
502
+ } else if (keyDownFlags["KeyX"]) {
503
+ dragMode = "rotateX";
504
+ } else if (keyDownFlags["KeyC"]) {
505
+ dragMode = "rotateY";
506
+ }
507
+ } else if (keyDownFlags["Space"]) {
508
+ dragMarks[personIndex] =
509
+ poseData[personIndex].map(
510
+ (node) => node != null && distSq(p, node) < dragRange ** 2);
511
+ isDragging = dragMarks[personIndex].some((mark) => mark);
512
+ dragMode = "move";
513
+ } else if (minDist < 16) {
514
+ dragMarks[personIndex][nodeIndex] = true;
515
+ isDragging = true;
516
+ dragMode = "move";
517
+ }
518
+ }
519
+
520
+ // Canvas要素上でマウスが動いた場合に呼び出される関数
521
+ function handleMouseMove(event) {
522
+ mouseCursor = getCanvasPosition(event);
523
+ if (isDragging) {
524
+ const p = getCanvasPosition(event);
525
+ const dragOffset = [p[0] - dragStart[0], p[1] - dragStart[1]];
526
+ const latestPoseData = fetchLatestPoseData();
527
+
528
+ if (dragMode == "scale") {
529
+ // 拡大縮小
530
+ let xScale = 1 + dragOffset[0] / canvas.width;
531
+ let yScale = 1 + dragOffset[0] / canvas.height;
532
+ forEachMarkedNodes((i, j, node) => {
533
+ const lp = latestPoseData[i][j];
534
+ node[0] = (lp[0] - dragStart[0]) * xScale + dragStart[0];
535
+ node[1] = (lp[1] - dragStart[1]) * yScale + dragStart[1];
536
+ });
537
+ } else if (dragMode == "rotate") {
538
+ rotateBaseVector = dragOffset;
539
+ if (!event.shiftKey) {
540
+ dragMode = "rotate2";
541
+ }
542
+ } else if (dragMode == "rotate2") {
543
+ // 回転
544
+ let angle = directedAngleTo(rotateBaseVector, dragOffset);
545
+ forEachMarkedNodes((i, j, node) => {
546
+ const lp = latestPoseData[i][j];
547
+ let x = lp[0] - dragStart[0];
548
+ let y = lp[1] - dragStart[1];
549
+ let sin = Math.sin(angle);
550
+ let cos = Math.cos(angle);
551
+ node[0] = x * cos - y * sin + dragStart[0];
552
+ node[1] = x * sin + y * cos + dragStart[1];
553
+ });
554
+ } else if (dragMode == "rotateX") {
555
+ const center = dragStart;
556
+ const angle = dragOffset[1] / -40;
557
+ forEachMarkedNodes((i, j, node) => {
558
+ const lp = latestPoseData[i][j];
559
+ const np = rotateAndProject(rotateX, lp, center, angle);
560
+ node[0] = np[0];
561
+ node[1] = np[1];
562
+ });
563
+ } else if (dragMode == "rotateY") {
564
+ const center = dragStart;
565
+ const angle = dragOffset[0] / 40;
566
+ forEachMarkedNodes((i, j, node) => {
567
+ const lp = latestPoseData[i][j];
568
+ const np = rotateAndProject(rotateY, lp, center, angle);
569
+ node[0] = np[0];
570
+ node[1] = np[1];
571
+ });
572
+ } else if (dragMode == "move") {
573
+ // 移動
574
+ forEachMarkedNodes((i, j, node) => {
575
+ const lp = latestPoseData[i][j];
576
+ node[0] = lp[0] + dragOffset[0];
577
+ node[1] = lp[1] + dragOffset[1];
578
+ });
579
+ }
580
+
581
+ // animatePicture(document.getElementById("canvas4"),[canvas.width, canvas.height], history[0][0], poseData[0], pictureImageData, initialPoseField);
582
+ }
583
+
584
+ Redraw();
585
+ }
586
+
587
+ function handleMouseUp(event) {
588
+ console.profile("animate");
589
+ animatePicture(
590
+ document.getElementById("canvas4"),
591
+ [canvas.width, canvas.height], history[0][0], poseData[0], pictureImageData, initialPoseField);
592
+ console.profileEnd("animate");
593
+ isDragging = false;
594
+ addHistory();
595
+ Redraw();
596
+ }
597
+
598
+ function handleMouseLeave(event) {
599
+ mouseCursor = [-1,-1];
600
+ if (isDragging) {
601
+ handleMouseUp(event);
602
+ }
603
+ keyDownFlags = {};
604
+ }
605
+
606
+ function ModifyDragRange(delta) { dragRange = Math.max(dragRangeDelta, Math.min(512, dragRange + delta)); }
607
+
608
+ document.addEventListener('wheel', function(event) {
609
+ if (!isMouseOnCanvas()) {return;}
610
+ if (!event.altKey && !keyDownFlags['Space']) {return;}
611
+
612
+ event.preventDefault();
613
+ const deltaY = event.deltaY;
614
+ if (deltaY < 0) {ModifyDragRange(-dragRangeDelta);}
615
+ if (0 < deltaY) {ModifyDragRange(dragRangeDelta);}
616
+ lastWheeling = new Date().getTime();
617
+ Redraw();
618
+ window.setTimeout(function() { Redraw(); }, wheelDisplayTime+10);
619
+ }, {passive: false});
620
+
621
+ document.addEventListener("keydown", (event) => {
622
+ if (!isMouseOnCanvas()) {return;}
623
+
624
+ if (event.code == "BracketLeft") { ModifyDragRange(-dragRangeDelta); }
625
+ if (event.code == "BracketRight") { ModifyDragRange(dragRangeDelta); }
626
+ keyDownFlags[event.code] = true;
627
+ Redraw();
628
+ event.preventDefault();
629
+ });
630
+ document.addEventListener("keyup", (event) => {
631
+ if (!isMouseOnCanvas()) {return;}
632
+
633
+ keyDownFlags[event.code] = false;
634
+ if (event.ctrlKey && event.code == "KeyE") {
635
+ addPerson();
636
+ } else if (event.ctrlKey && event.code == "KeyZ") {
637
+ if (event.shiftKey) {
638
+ redo();
639
+ } else {
640
+ undo();
641
+ }
642
+ }
643
+ Redraw();
644
+ event.preventDefault();
645
+ });
646
+
647
+ function initializeEditor() {
648
+ console.log("initializeEditor");
649
+
650
+ canvas = document.getElementById('canvas');
651
+ ctx = canvas.getContext('2d');
652
+
653
+ canvas.addEventListener('mousedown', handleMouseDown);
654
+ canvas.addEventListener('mousemove', handleMouseMove);
655
+ canvas.addEventListener('mouseup', handleMouseUp);
656
+ canvas.addEventListener('mouseleave', handleMouseLeave);
657
+ poseData = [];
658
+ clearHistory();
659
+ }
660
+
661
+ function importPose(jsonData) {
662
+ if (jsonData != null) {
663
+ newPoseData = makePoseDataFromCandidateAndSubset(jsonData.candidate, jsonData.subset);
664
+ if (jsonData.width != null && jsonData.height != null) {
665
+ resizeCanvas(jsonData.width, jsonData.height);
666
+ }
667
+ } else {
668
+ newPoseData = makePoseDataFromCandidateAndSubset(sampleCandidateSource, [sampleSubsetElementSource]);
669
+ }
670
+ poseData = poseData.concat(newPoseData);
671
+ addHistory();
672
+ Redraw();
673
+ }
674
+
675
+ // crc32
676
+ // CRC32を初期化
677
+ function initCrc32Table() {
678
+ const crcTable = new Uint32Array(256);
679
+ for (let i = 0; i < 256; i++) {
680
+ let c = i;
681
+ for (let j = 0; j < 8; j++) {
682
+ c = (c & 1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1);
683
+ }
684
+ crcTable[i] = c;
685
+ }
686
+ return crcTable;
687
+ }
688
+
689
+ // データのCRC32を計算
690
+ function getCrc32(data, crc=0) {
691
+ const crcTable = initCrc32Table();
692
+ crc = (crc ^ 0xFFFFFFFF) >>> 0;
693
+ for (let i = 0; i < data.length; i++) {
694
+ crc = crcTable[(crc ^ data[i]) & 0xFF] ^ (crc >>> 8);
695
+ }
696
+ return (crc ^ 0xFFFFFFFF) >>> 0;
697
+ }
698
+
699
+ function stringToUint8Array(str) {
700
+ var arr = new Uint8Array(str.length);
701
+ for (var i = 0; i < str.length; i++) {
702
+ arr[i] = str.charCodeAt(i);
703
+ }
704
+ return arr;
705
+ }
706
+
707
+ function base64ToUint8Array(base64Str) {
708
+ return stringToUint8Array(atob(base64Str));
709
+ }
710
+
711
+ function visitPng(png, type) {
712
+ var dataLength;
713
+ var chunkType;
714
+ var nextChunkPos;
715
+ var Signature = String.fromCharCode(137, 80, 78, 71, 13, 10, 26, 10);
716
+ var rpos = 0;
717
+
718
+ // シグネチャの確認
719
+ if (String.fromCharCode.apply(null, png.subarray(rpos, rpos += 8)) !== Signature) {
720
+ throw new Error('invalid signature');
721
+ }
722
+
723
+ // チャンクの探索
724
+ while (rpos < png.length) {
725
+ dataLength = (
726
+ (png[rpos++] << 24) |
727
+ (png[rpos++] << 16) |
728
+ (png[rpos++] << 8) |
729
+ (png[rpos++] )
730
+ ) >>> 0;
731
+
732
+ nextChunkPos = rpos + dataLength + 8;
733
+
734
+ chunkType = String.fromCharCode.apply(null, png.subarray(rpos, rpos += 4));
735
+
736
+ if (chunkType === type) {
737
+ return [rpos - 8, dataLength, nextChunkPos];
738
+ }
739
+
740
+ rpos = nextChunkPos;
741
+ }
742
+ }
743
+
744
+ function createChunk(type, data) {
745
+ var dataLength = data.length;
746
+ var chunk = new Uint8Array(4 + 4 + dataLength + 4);
747
+ var type = stringToUint8Array(type);
748
+ var pos = 0;
749
+
750
+ // length
751
+ chunk[pos++] = (dataLength >> 24) & 0xff;
752
+ chunk[pos++] = (dataLength >> 16) & 0xff;
753
+ chunk[pos++] = (dataLength >> 8) & 0xff;
754
+ chunk[pos++] = (dataLength ) & 0xff;
755
+
756
+ // type
757
+ chunk[pos++] = type[0];
758
+ chunk[pos++] = type[1];
759
+ chunk[pos++] = type[2];
760
+ chunk[pos++] = type[3];
761
+
762
+ // data
763
+ for (let i = 0; i < dataLength; ++i) {
764
+ chunk[pos++] = data[i];
765
+ }
766
+
767
+ //crc
768
+ initCrc32Table();
769
+ let crc = getCrc32(type);
770
+ crc = getCrc32(data, crc);
771
+ chunk[pos++] = (crc >> 24) & 0xff;
772
+ chunk[pos++] = (crc >> 16) & 0xff;
773
+ chunk[pos++] = (crc >> 8) & 0xff;
774
+ chunk[pos++] = (crc ) & 0xff;
775
+
776
+ return chunk;
777
+ }
778
+
779
+ function insertChunk(destBuffer, sourceBuffer, rpos, chunk) {
780
+ var pos = 0;
781
+
782
+ // IDAT チャンクの前までコピー
783
+ destBuffer.set(sourceBuffer.subarray(0, rpos), pos);
784
+ pos += rpos;
785
+
786
+ // hoGe チャンクをコピー
787
+ destBuffer.set(chunk, pos);
788
+ pos += chunk.length;
789
+
790
+ // IDAT チャンク以降をコピー
791
+ destBuffer.set(sourceBuffer.subarray(rpos), pos);
792
+ }
793
+
794
+ function mergeCanvasWithPose(keyword, content) {
795
+ const canvasUrl = canvas.toDataURL();
796
+
797
+ var insertion = stringToUint8Array(`${keyword}\0${content}`);
798
+ var chunk = createChunk("tEXt", insertion);
799
+ var sourceBuffer = base64ToUint8Array(canvasUrl.split(',')[1]);
800
+ var destBuffer = new Uint8Array(sourceBuffer.length + insertion.length + 12);
801
+
802
+ var [rpos, dataLength, nextChunkPos] = visitPng(sourceBuffer, "IHDR");
803
+ insertChunk(destBuffer, sourceBuffer, nextChunkPos, chunk);
804
+
805
+ var blob = new Blob([destBuffer], {type: "image/png"});
806
+ var url = URL.createObjectURL(blob);
807
+ return url;
808
+ }
809
+
810
+ function savePose() {
811
+ var [candidate, subset] = poseDataToCandidateAndSubset(poseData);
812
+ let jsonData = {candidate: candidate, subset: subset};
813
+
814
+ var url = mergeCanvasWithPose("openpose", JSON.stringify(jsonData));
815
+
816
+ const createEl = document.createElement('a');
817
+ createEl.href = url;
818
+
819
+ // This is the name of our downloaded file
820
+ createEl.download = "pose.png";
821
+
822
+ createEl.click();
823
+ createEl.remove();
824
+
825
+ return jsonData;
826
+ }
827
+
828
+ function importPicture(image) {
829
+ var pic = new Image();
830
+ pic.onload = () => {
831
+ const size = [canvas.width, canvas.height];
832
+
833
+ const canvas3 = document.getElementById("canvas3");
834
+ pictureImageData = makeImageDataFromPicture(canvas3, size, pic);
835
+
836
+ const canvas4 = document.getElementById("canvas4");
837
+ offscreen = canvas4;
838
+
839
+ initialPoseField = buildWeightMap(size, poseData[0]);
840
+ animatePicture(canvas4, size, poseData[0], poseData[0], pictureImageData, initialPoseField);
841
+
842
+ Redraw();
843
+ };
844
+ pic.src = image;
845
+
846
+ initMicroscope();
847
+ }
848
+
849
+ function importBackground(image) {
850
+ if (image == null) {
851
+ canvasBg = null;
852
+ Redraw();
853
+ return;
854
+ }
855
+
856
+ let m = new Image();
857
+ m.src = image;
858
+ m.onload = function() {
859
+ canvasBg = m;
860
+ Redraw();
861
+ }
862
+ }
main.py ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import json as js
3
+ import util
4
+ from fileservice import app
5
+ from fastapi.staticfiles import StaticFiles
6
+ from pose import infer, draw
7
+ from abgr import remove_bg, split_image
8
+
9
+ def image_changed(image):
10
+ if image == None:
11
+ return "estimation", {}
12
+
13
+ print("make mask")
14
+ cvimage = util.pil2cv(image)
15
+ mask, fg, bg = split_image(cvimage[..., ::-1])
16
+
17
+ print("pose not found")
18
+ pose_result, _ = infer(cvimage)
19
+
20
+ candidate, subset = util.convert_to_openpose(pose_result)
21
+ candidateJson = util.candidate_to_json_string(candidate)
22
+ subsetJson = util.subset_to_json_string(subset)
23
+ jsonText = f'{{"candidate":{candidateJson}, "subset":{subsetJson}, "width":{image.width}, "height":{image.height}}}'
24
+ return f'{image.width}px x {image.height}px, {len(subset)} indivisual(s)', jsonText, mask, fg, bg
25
+
26
+ with gr.Blocks(css="""button { min-width: 80px; }""") as demo:
27
+ with gr.Row():
28
+ with gr.Column(scale=1):
29
+ source = gr.Image(type="pil")
30
+ info = gr.Markdown("""info""")
31
+ btn = gr.Button("Import")
32
+ mask = gr.Image()
33
+ frontImage = gr.Image(image_mode="RGBA")
34
+ backImage = gr.Image(image_mode="RGBA")
35
+ with gr.Accordion(label="Json", open=False):
36
+ json = gr.JSON(label="Json")
37
+ with gr.Column(scale=3):
38
+ gr.HTML('<canvas id="canvas" width="512" height="512"></canvas>')
39
+ with gr.Row():
40
+ gr.HTML('<canvas id="canvas2" width="512" height="512" style="display:none"></canvas>')
41
+ gr.HTML('<canvas id="canvas3" width="512" height="512" style="display:none"></canvas>')
42
+ gr.HTML('<canvas id="canvas4" width="512" height="512" style="display:none"></canvas>')
43
+ gr.HTML('<canvas id="microscope" width="50" height="50" style="display:none"></canvas>')
44
+
45
+ source.change(
46
+ fn = image_changed,
47
+ inputs = [source],
48
+ outputs = [info, json, mask, frontImage, backImage])
49
+
50
+ btn.click(
51
+ fn = None,
52
+ inputs = [frontImage, backImage, json],
53
+ outputs = [],
54
+ _js="(frontImage, backImage, json) => { initializeEditor(); importPose(json); importPicture(frontImage); importBackground(backImage); return []; }")
55
+
56
+ demo.load(fn=None, inputs=[], outputs=[], _js="() => { initializeEditor(); importPose(); importPicture('./static/sample2.png'); return []; }")
57
+
58
+ print("mount")
59
+ app.mount("/static", StaticFiles(directory="static"), name="static")
60
+ app.mount("/js", StaticFiles(directory="js"), name="js")
61
+ gr.mount_gradio_app(app, demo, path="/")
pose.py ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from mmpose.apis import (inference_top_down_pose_model, init_pose_model,
2
+ process_mmdet_results, vis_pose_result)
3
+ from mmpose.datasets import DatasetInfo
4
+ from mmdet.apis import inference_detector, init_detector
5
+
6
+ det_model = init_detector(
7
+ "./external/faster_rcnn_r50_fpn_coco.py",
8
+ "./faster_rcnn_r50_fpn_1x_coco_20200130-047c8118.pth",
9
+ device="cpu")
10
+ pose_model = init_pose_model(
11
+ "./external/hrnet_w48_coco_256x192.py",
12
+ "./hrnet_w48_coco_256x192-b9e0b3ab_20200708.pth",
13
+ device="cpu")
14
+
15
+ dataset = pose_model.cfg.data['test']['type']
16
+ dataset_info = pose_model.cfg.data['test'].get('dataset_info', None)
17
+
18
+ dataset_info = DatasetInfo(dataset_info)
19
+
20
+ def infer(image):
21
+ mmdet_results = inference_detector(det_model, image)
22
+ person_results = process_mmdet_results(mmdet_results, 1)
23
+
24
+ pose_results, returned_outputs = inference_top_down_pose_model(
25
+ pose_model,
26
+ image,
27
+ person_results,
28
+ bbox_thr=0.3,
29
+ format='xyxy',
30
+ dataset=dataset,
31
+ dataset_info=dataset_info,
32
+ return_heatmap=False,
33
+ outputs=None)
34
+ # print(pose_results)
35
+ # print(returned_outputs)
36
+
37
+ return pose_results, returned_outputs
38
+
39
+ def draw(image, results):
40
+ return vis_pose_result(
41
+ pose_model,
42
+ image,
43
+ results,
44
+ dataset=dataset,
45
+ dataset_info=dataset_info,
46
+ kpt_score_thr=0.3,
47
+ radius=4,
48
+ thickness=3,
49
+ show=False,
50
+ out_file=None)
requirements.txt ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ fastapi==0.92.0
2
+ gradio==3.18.0
3
+ numpy==1.23.5
4
+ opencv_python
5
+ scipy
6
+ torch
7
+ torchvision
8
+ openmim
9
+ huggingface-hub
10
+ onnxruntime-gpu
11
+
12
+
static/sample.png ADDED
static/sample2.png ADDED
static/sample3.png ADDED
util.py ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ import cv2
3
+
4
+ def pil2cv(image):
5
+ ''' PIL型 -> OpenCV型 '''
6
+ new_image = np.array(image, dtype=np.uint8)
7
+ if new_image.ndim == 2: # モノクロ
8
+ pass
9
+ elif new_image.shape[2] == 3: # カラー
10
+ new_image = cv2.cvtColor(new_image, cv2.COLOR_RGB2BGR)
11
+ elif new_image.shape[2] == 4: # 透過
12
+ new_image = cv2.cvtColor(new_image, cv2.COLOR_RGBA2BGRA)
13
+ return new_image
14
+
15
+ def candidate_to_json_string(arr):
16
+ a = [f'[{x:.2f}, {y:.2f}]' for x, y, *_ in arr]
17
+ return '[' + ', '.join(a) + ']'
18
+
19
+ # make subset to json
20
+ def subset_to_json_string(arr):
21
+ arr_str = ','.join(['[' + ','.join([f'{num:.2f}' for num in row]) + ']' for row in arr])
22
+ return '[' + arr_str + ']'
23
+
24
+ keypoint_index_mapping = [
25
+ 0,
26
+ 17,
27
+ 6,
28
+ 8,
29
+ 10,
30
+ 5,
31
+ 7,
32
+ 9,
33
+ 12,
34
+ 14,
35
+ 16,
36
+ 11,
37
+ 13,
38
+ 15,
39
+ 2,
40
+ 1,
41
+ 4,
42
+ 3,
43
+ ]
44
+
45
+ def convert_keypoints(keypoints):
46
+ return [keypoints[i] for i in keypoint_index_mapping]
47
+
48
+ def convert_to_openpose(pose_result):
49
+ candidate = []
50
+ subset = []
51
+ for d in pose_result:
52
+ n = len(candidate)
53
+ if d['bbox'][4] < 0.9:
54
+ continue
55
+ keypoints = d['keypoints'][:, :2].tolist()
56
+ midpoint = [(keypoints[5][0] + keypoints[6][0]) / 2, (keypoints[5][1] + keypoints[6][1]) / 2]
57
+ keypoints.append(midpoint)
58
+ candidate.extend(convert_keypoints(keypoints))
59
+ m = len(candidate)
60
+ subset.append([j for j in range(n, m)])
61
+
62
+ return candidate, subset
63
+