DragDiffusion / utils /drag_utils.py
peter850421's picture
Upload folder using huggingface_hub
e1ebf71
# *************************************************************************
# Copyright (2023) Bytedance Inc.
#
# Copyright (2023) DragDiffusion Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# *************************************************************************
import copy
import torch
import torch.nn.functional as F
def point_tracking(F0,
F1,
handle_points,
handle_points_init,
args):
with torch.no_grad():
for i in range(len(handle_points)):
pi0, pi = handle_points_init[i], handle_points[i]
f0 = F0[:, :, int(pi0[0]), int(pi0[1])]
r1, r2 = int(pi[0])-args.r_p, int(pi[0])+args.r_p+1
c1, c2 = int(pi[1])-args.r_p, int(pi[1])+args.r_p+1
F1_neighbor = F1[:, :, r1:r2, c1:c2]
all_dist = (f0.unsqueeze(dim=-1).unsqueeze(dim=-1) - F1_neighbor).abs().sum(dim=1)
all_dist = all_dist.squeeze(dim=0)
# WARNING: no boundary protection right now
row, col = divmod(all_dist.argmin().item(), all_dist.shape[-1])
handle_points[i][0] = pi[0] - args.r_p + row
handle_points[i][1] = pi[1] - args.r_p + col
return handle_points
def check_handle_reach_target(handle_points,
target_points):
# dist = (torch.cat(handle_points,dim=0) - torch.cat(target_points,dim=0)).norm(dim=-1)
all_dist = list(map(lambda p,q: (p-q).norm(), handle_points, target_points))
return (torch.tensor(all_dist) < 2.0).all()
# obtain the bilinear interpolated feature patch centered around (x, y) with radius r
def interpolate_feature_patch(feat,
y,
x,
r):
x0 = torch.floor(x).long()
x1 = x0 + 1
y0 = torch.floor(y).long()
y1 = y0 + 1
wa = (x1.float() - x) * (y1.float() - y)
wb = (x1.float() - x) * (y - y0.float())
wc = (x - x0.float()) * (y1.float() - y)
wd = (x - x0.float()) * (y - y0.float())
Ia = feat[:, :, y0-r:y0+r+1, x0-r:x0+r+1]
Ib = feat[:, :, y1-r:y1+r+1, x0-r:x0+r+1]
Ic = feat[:, :, y0-r:y0+r+1, x1-r:x1+r+1]
Id = feat[:, :, y1-r:y1+r+1, x1-r:x1+r+1]
return Ia * wa + Ib * wb + Ic * wc + Id * wd
def drag_diffusion_update(model,
init_code,
t,
handle_points,
target_points,
mask,
args):
assert len(handle_points) == len(target_points), \
"number of handle point must equals target points"
text_emb = model.get_text_embeddings(args.prompt).detach()
# the init output feature of unet
with torch.no_grad():
unet_output, F0 = model.forward_unet_features(init_code, t, encoder_hidden_states=text_emb,
layer_idx=args.unet_feature_idx, interp_res_h=args.sup_res, interp_res_w=args.sup_res)
x_prev_0,_ = model.step(unet_output, t, init_code)
# init_code_orig = copy.deepcopy(init_code)
# prepare optimizable init_code and optimizer
init_code.requires_grad_(True)
optimizer = torch.optim.Adam([init_code], lr=args.lr)
# prepare for point tracking and background regularization
handle_points_init = copy.deepcopy(handle_points)
interp_mask = F.interpolate(mask, (init_code.shape[2],init_code.shape[3]), mode='nearest')
# prepare amp scaler for mixed-precision training
scaler = torch.cuda.amp.GradScaler()
gif_init_code = []
for step_idx in range(args.n_pix_step):
with torch.autocast(device_type='cuda', dtype=torch.float16):
unet_output, F1 = model.forward_unet_features(init_code, t, encoder_hidden_states=text_emb,
layer_idx=args.unet_feature_idx, interp_res_h=args.sup_res, interp_res_w=args.sup_res)
x_prev_updated,_ = model.step(unet_output, t, init_code)
# do point tracking to update handle points before computing motion supervision loss
if step_idx != 0:
handle_points = point_tracking(F0, F1, handle_points, handle_points_init, args)
print('new handle points', handle_points)
# break if all handle points have reached the targets
if check_handle_reach_target(handle_points, target_points):
break
loss = 0.0
for i in range(len(handle_points)):
pi, ti = handle_points[i], target_points[i]
# skip if the distance between target and source is less than 1
if (ti - pi).norm() < 2.:
continue
di = (ti - pi) / (ti - pi).norm()
# motion supervision
f0_patch = F1[:,:,int(pi[0])-args.r_m:int(pi[0])+args.r_m+1, int(pi[1])-args.r_m:int(pi[1])+args.r_m+1].detach()
f1_patch = interpolate_feature_patch(F1, pi[0] + di[0], pi[1] + di[1], args.r_m)
loss += ((2*args.r_m+1)**2)*F.l1_loss(f0_patch, f1_patch)
# masked region must stay unchanged
loss += args.lam * ((x_prev_updated-x_prev_0)*(1.0-interp_mask)).abs().sum()
# loss += args.lam * ((init_code_orig-init_code)*(1.0-interp_mask)).abs().sum()
print('loss total=%f'%(loss.item()))
scaler.scale(loss).backward()
scaler.step(optimizer)
scaler.update()
optimizer.zero_grad()
if args.create_gif_checkbox:
if step_idx % args.gif_interval == 0 or step_idx == args.n_pix_step - 1:
gif_init_code.append(init_code)
return init_code, gif_init_code
def drag_diffusion_update_gen(model,
init_code,
t,
handle_points,
target_points,
mask,
args):
assert len(handle_points) == len(target_points), \
"number of handle point must equals target points"
# positive prompt embedding
text_emb = model.get_text_embeddings(args.prompt).detach()
if args.guidance_scale > 1.0:
unconditional_input = model.tokenizer(
[args.neg_prompt],
padding="max_length",
max_length=77,
return_tensors="pt"
)
unconditional_emb = model.text_encoder(unconditional_input.input_ids.to(text_emb.device))[0].detach()
text_emb = torch.cat([unconditional_emb, text_emb], dim=0)
# the init output feature of unet
with torch.no_grad():
if args.guidance_scale > 1.:
model_inputs_0 = copy.deepcopy(torch.cat([init_code] * 2))
else:
model_inputs_0 = copy.deepcopy(init_code)
unet_output, F0 = model.forward_unet_features(model_inputs_0, t, encoder_hidden_states=text_emb,
layer_idx=args.unet_feature_idx, interp_res_h=args.sup_res_h, interp_res_w=args.sup_res_w)
if args.guidance_scale > 1.:
# strategy 1: discard the unconditional branch feature maps
# F0 = F0[1].unsqueeze(dim=0)
# strategy 2: concat pos and neg branch feature maps for motion-sup and point tracking
# F0 = torch.cat([F0[0], F0[1]], dim=0).unsqueeze(dim=0)
# strategy 3: concat pos and neg branch feature maps with guidance_scale consideration
coef = args.guidance_scale / (2*args.guidance_scale - 1.0)
F0 = torch.cat([(1-coef)*F0[0], coef*F0[1]], dim=0).unsqueeze(dim=0)
unet_output_uncon, unet_output_con = unet_output.chunk(2, dim=0)
unet_output = unet_output_uncon + args.guidance_scale * (unet_output_con - unet_output_uncon)
x_prev_0,_ = model.step(unet_output, t, init_code)
# init_code_orig = copy.deepcopy(init_code)
# prepare optimizable init_code and optimizer
init_code.requires_grad_(True)
optimizer = torch.optim.Adam([init_code], lr=args.lr)
# prepare for point tracking and background regularization
handle_points_init = copy.deepcopy(handle_points)
interp_mask = F.interpolate(mask, (init_code.shape[2],init_code.shape[3]), mode='nearest')
# prepare amp scaler for mixed-precision training
scaler = torch.cuda.amp.GradScaler()
gif_init_code = []
handle_points_list = []
for step_idx in range(args.n_pix_step):
with torch.autocast(device_type='cuda', dtype=torch.float16):
if args.guidance_scale > 1.:
model_inputs = init_code.repeat(2,1,1,1)
else:
model_inputs = init_code
unet_output, F1 = model.forward_unet_features(model_inputs, t, encoder_hidden_states=text_emb,
layer_idx=args.unet_feature_idx, interp_res_h=args.sup_res_h, interp_res_w=args.sup_res_w)
if args.guidance_scale > 1.:
# strategy 1: discard the unconditional branch feature maps
# F1 = F1[1].unsqueeze(dim=0)
# strategy 2: concat positive and negative branch feature maps for motion-sup and point tracking
# F1 = torch.cat([F1[0], F1[1]], dim=0).unsqueeze(dim=0)
# strategy 3: concat pos and neg branch feature maps with guidance_scale consideration
coef = args.guidance_scale / (2*args.guidance_scale - 1.0)
F1 = torch.cat([(1-coef)*F1[0], coef*F1[1]], dim=0).unsqueeze(dim=0)
unet_output_uncon, unet_output_con = unet_output.chunk(2, dim=0)
unet_output = unet_output_uncon + args.guidance_scale * (unet_output_con - unet_output_uncon)
x_prev_updated,_ = model.step(unet_output, t, init_code)
# do point tracking to update handle points before computing motion supervision loss
if step_idx != 0:
handle_points = point_tracking(F0, F1, handle_points, handle_points_init, args)
print('new handle points', handle_points)
print('target points', target_points)
# break if all handle points have reached the targets
if check_handle_reach_target(handle_points, target_points):
break
loss = 0.0
for i in range(len(handle_points)):
pi, ti = handle_points[i], target_points[i]
# skip if the distance between target and source is less than 1
if (ti - pi).norm() < 2.:
continue
di = (ti - pi) / (ti - pi).norm()
# motion supervision
f0_patch = F1[:,:,int(pi[0])-args.r_m:int(pi[0])+args.r_m+1, int(pi[1])-args.r_m:int(pi[1])+args.r_m+1].detach()
f1_patch = interpolate_feature_patch(F1, pi[0] + di[0], pi[1] + di[1], args.r_m)
loss += ((2*args.r_m+1)**2)*F.l1_loss(f0_patch, f1_patch)
# masked region must stay unchanged
loss += args.lam * ((x_prev_updated-x_prev_0)*(1.0-interp_mask)).abs().sum()
# loss += args.lam * ((init_code_orig - init_code)*(1.0-interp_mask)).abs().sum()
print('loss total=%f'%(loss.item()))
if args.create_gif_checkbox:
if step_idx % args.gif_interval == 0 or step_idx == args.n_pix_step - 1:
gif_init_code.append(init_code)
if args.create_tracking_points_checkbox:
if step_idx % args.gif_interval == 0 or step_idx == args.n_pix_step - 1:
handle_points_list.append(handle_points)
scaler.scale(loss).backward()
scaler.step(optimizer)
scaler.update()
optimizer.zero_grad()
return init_code, gif_init_code, handle_points_list