File size: 6,864 Bytes
cf1a3cd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3338ab8
cf1a3cd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
"""
Interpolation Module
Handles latent space interpolation for smooth frame transitions.
"""

import torch
from diffusers import AutoencoderKL, StableDiffusionPipeline
from PIL import Image
from typing import List, Optional
import numpy as np


class LatentInterpolator:
    """
    Interpolates between keyframes in latent space for smooth animations.
    """
    
    def __init__(
        self,
        model_id: str = "Manojb/stable-diffusion-2-1-base",
        device: str = "cuda" if torch.cuda.is_available() else "mps" if torch.backends.mps.is_available() else "cpu",
        torch_dtype: torch.dtype = torch.float16 if torch.cuda.is_available() else torch.float32
    ):
        """
        Initialize the interpolator.
        
        Args:
            model_id: Stable Diffusion model for VAE
            device: Device to run on
            torch_dtype: Data type for tensors
        """
        self.device = device
        self.torch_dtype = torch_dtype
        
        print(f"Loading VAE from: {model_id}")
        
        # Load VAE for encoding/decoding
        self.vae = AutoencoderKL.from_pretrained(
            model_id,
            subfolder="vae",
            torch_dtype=torch_dtype
        ).to(device)
        
        print("VAE loaded successfully!")
    
    def encode_image(self, image: Image.Image) -> torch.Tensor:
        """
        Encode PIL Image to latent representation.
        
        Args:
            image: PIL Image to encode
            
        Returns:
            Latent tensor
        """
        # Preprocess image
        image = image.resize((512, 512))
        image_np = np.array(image).astype(np.float32) / 255.0
        image_np = image_np * 2.0 - 1.0  # Normalize to [-1, 1]
        
        # Convert to tensor
        image_tensor = torch.from_numpy(image_np).permute(2, 0, 1).unsqueeze(0)
        image_tensor = image_tensor.to(device=self.device, dtype=self.torch_dtype)
        
        # Encode to latent space
        with torch.no_grad():
            latent = self.vae.encode(image_tensor).latent_dist.sample()
            latent = latent * 0.18215  # Scaling factor for SD
        
        return latent
    
    def decode_latent(self, latent: torch.Tensor) -> Image.Image:
        """
        Decode latent representation to PIL Image.
        
        Args:
            latent: Latent tensor
            
        Returns:
            PIL Image
        """
        # Decode from latent space
        with torch.no_grad():
            latent = latent / 0.18215
            image_tensor = self.vae.decode(latent).sample
        
        # Post-process
        image_tensor = (image_tensor / 2 + 0.5).clamp(0, 1)
        image_np = image_tensor.cpu().permute(0, 2, 3, 1).numpy()[0]
        image_np = (image_np * 255).astype(np.uint8)
        
        return Image.fromarray(image_np)
    
    def interpolate_latents(
        self,
        latent_start: torch.Tensor,
        latent_end: torch.Tensor,
        num_steps: int = 10,
        interpolation_type: str = "linear"
    ) -> List[torch.Tensor]:
        """
        Interpolate between two latent representations.
        
        Args:
            latent_start: Starting latent
            latent_end: Ending latent
            num_steps: Number of interpolation steps
            interpolation_type: Type of interpolation (linear, slerp)
            
        Returns:
            List of interpolated latents
        """
        interpolated = []
        
        for i in range(num_steps):
            alpha = i / (num_steps - 1) if num_steps > 1 else 0
            
            if interpolation_type == "linear":
                # Linear interpolation
                latent = (1 - alpha) * latent_start + alpha * latent_end
            elif interpolation_type == "slerp":
                # Spherical linear interpolation (better for latent spaces)
                latent = self._slerp(latent_start, latent_end, alpha)
            else:
                raise ValueError(f"Unknown interpolation type: {interpolation_type}")
            
            interpolated.append(latent)
        
        return interpolated
    
    def _slerp(
        self,
        v0: torch.Tensor,
        v1: torch.Tensor,
        t: float,
        eps: float = 1e-8
    ) -> torch.Tensor:
        """
        Spherical linear interpolation.
        
        Args:
            v0: Start vector
            v1: End vector
            t: Interpolation parameter [0, 1]
            eps: Small value to prevent division by zero
            
        Returns:
            Interpolated tensor
        """
        # Normalize vectors
        v0_norm = v0 / (torch.norm(v0) + eps)
        v1_norm = v1 / (torch.norm(v1) + eps)
        
        # Compute angle
        dot = (v0_norm * v1_norm).sum()
        dot = torch.clamp(dot, -1.0, 1.0)
        omega = torch.acos(dot)
        
        # Compute interpolation
        so = torch.sin(omega)
        if so < eps:
            # Vectors are nearly parallel, use linear interpolation
            return (1.0 - t) * v0 + t * v1
        
        return (torch.sin((1.0 - t) * omega) / so) * v0 + (torch.sin(t * omega) / so) * v1
    
    def generate_interpolated_sequence(
        self,
        keyframes: List[Image.Image],
        frames_between: int = 4,
        interpolation_type: str = "slerp"
    ) -> List[Image.Image]:
        """
        Generate smooth sequence by interpolating between keyframes.
        
        Args:
            keyframes: List of keyframe images
            frames_between: Number of frames to generate between each keyframe
            interpolation_type: Interpolation method
            
        Returns:
            List of all frames (keyframes + interpolated)
        """
        if len(keyframes) < 2:
            return keyframes
        
        all_frames = []
        
        for i in range(len(keyframes) - 1):
            # Encode keyframes to latent space
            latent_start = self.encode_image(keyframes[i])
            latent_end = self.encode_image(keyframes[i + 1])
            
            # Interpolate
            interpolated_latents = self.interpolate_latents(
                latent_start,
                latent_end,
                num_steps=frames_between + 2,  # +2 to include start and end
                interpolation_type=interpolation_type
            )
            
            # Decode all except the last (avoid duplicates)
            for latent in interpolated_latents[:-1]:
                frame = self.decode_latent(latent)
                all_frames.append(frame)
        
        # Add final keyframe
        all_frames.append(keyframes[-1])
        
        return all_frames


if __name__ == "__main__":
    print("Latent interpolation module loaded.")
    print("Use with keyframe images to generate smooth interpolations.")