Views
No views yet
![]() Generated Video |
![]() Input |
1import torch
2from diffusers import WanImageToVideoPipeline, AutoencoderKLWan, WanTransformer3DModel, UniPCMultistepScheduler
3from diffusers.utils import export_to_video, load_image
4from huggingface_hub import hf_hub_download
5from typing import List, Tuple, Union
6from PIL import Image, ImageOps
7
8
9def pil_resize(
10 image: Image.Image,
11 target_size: Tuple[int, int],
12 pad_input: bool = False,
13 padding_color: Union[str, int, Tuple[int, ...]] = "white",
14) -> Image.Image:
15 """Resizing it to the target size.
16
17 Args:
18 image: Input image to be processed.
19 target_size: Target size (width, height).
20 pad_input: If set resizes the image while keeping the aspect ratio and pads the unfilled part.
21 padding_color: The color for the padded pixels.
22
23 Returns:
24 The resized image
25 """
26 if pad_input:
27 # Resize image, keep aspect ratio
28 image = ImageOps.contain(image, size=target_size)
29 # Pad while keeping image in center
30 image = ImageOps.pad(image, size=target_size, color=padding_color)
31 else:
32 image = image.resize(target_size)
33 return image
34
35
36def undo_pil_resize(
37 image: Image.Image,
38 target_size: Tuple[int, int],
39) -> Image.Image:
40 """Undo the resizing and padding of the input image to the a new image with size target_size.
41
42 Args:
43 image: Input image to be processed.
44 target_size: Target size (width, height).
45
46 Returns:
47 The resized image
48 """
49 tmp_img = Image.new(mode="RGB", size=target_size)
50 # Get the resized image size
51 tmp_img = ImageOps.contain(tmp_img, size=image.size)
52
53 # Undo padding by center cropping
54 width, height = image.size
55 tmp_width, tmp_height = tmp_img.size
56
57 left = int(round((width - tmp_width) / 2.0))
58 top = int(round((height - tmp_height) / 2.0))
59 right = left + tmp_width
60 bottom = top + tmp_height
61 cropped = image.crop((left, top, right, bottom))
62
63 # Undo resizing
64 ret = cropped.resize(target_size)
65 return ret
66
67# Set to True to save VRAM, slower inference
68enable_sequential_cpu_offload = True
69
70# Download the LoRA file
71lora_path = hf_hub_download(repo_id="Markus-Pobitzer/wlp-Wan2.2-TI2V-5B-lora", filename="base.safetensors")
72print(f"LoRA path: {lora_path}")
73
74# Loads the pipeline
75model_id = "Wan-AI/Wan2.2-TI2V-5B-Diffusers"
76vae = AutoencoderKLWan.from_pretrained(model_id, subfolder="vae", torch_dtype=torch.float32)
77pipe = WanImageToVideoPipeline.from_pretrained(model_id, vae=vae, dtype=torch.bfloat16)
78
79# Load LoRA
80pipe.load_lora_weights(lora_path)
81pipe.fuse_lora()
82
83# Either offload or directly to GPU
84if enable_sequential_cpu_offload:
85 pipe.enable_sequential_cpu_offload()
86else:
87 pipe.to("cuda")
88
89
90### INFERENCE ###
91image = load_image(
92 "https://uploads3.wikiart.org/images/claude-monet/haystacks-at-giverny.jpg"
93)
94og_size = image.size
95height = 480
96width = 832
97# Resize and pad
98ref_image = pil_resize(image, target_size=(width, height), pad_input=True)
99prompt = "Painting process step by step."
100
101output = pipe(
102 image=ref_image,
103 prompt=prompt,
104 height=height,
105 width=width,
106 num_frames=81,
107 output_type="pil",
108 guidance_scale=1.0,
109).frames[0]
110# To original image size
111output = [undo_pil_resize(img, og_size) for img in output][::-1]
112# Save video
113export_to_video(output, "output.mp4", fps=3)1@misc{pobitzer2025loomispainter,
2 title={Loomis Painter: Reconstructing the Painting Process},
3 author={Markus Pobitzer and Chang Liu and Chenyi Zhuang and Teng Long and Bin Ren and Nicu Sebe},
4 year={2025},
5 eprint={2511.17344},
6 archivePrefix={arXiv},
7 primaryClass={cs.CV},
8 url={https://arxiv.org/abs/2511.17344},
9}