@@ -52,6 +52,48 @@ def __init__(self, *args, **kwargs):
5252 )
5353
5454
55+ def _preprocess_conditioning_image (
56+ image : Image .Image | np .ndarray | torch .Tensor , height : int , width : int
57+ ) -> torch .Tensor :
58+ """Preprocess one Cosmos3 conditioning image to ``[1, 3, H, W]`` in ``[-1, 1]``."""
59+ if isinstance (image , Image .Image ):
60+ image = torch .from_numpy (np .array (image .convert ("RGB" ), copy = True )).permute (2 , 0 , 1 ).unsqueeze (0 )
61+ elif isinstance (image , np .ndarray ):
62+ image = torch .from_numpy (image )
63+ image = image .unsqueeze (0 ) if image .ndim == 3 else image
64+ image = image .permute (0 , 3 , 1 , 2 )
65+ else :
66+ image = image .unsqueeze (0 ) if image .ndim == 3 else image
67+
68+ if image .ndim != 4 or image .shape [0 ] != 1 or image .shape [1 ] != 3 :
69+ raise ValueError (f"`image` must describe one RGB image, got shape { tuple (image .shape )} ." )
70+
71+ is_integer_input = not image .is_floating_point ()
72+ image = image .to (dtype = torch .float32 )
73+ if not is_integer_input :
74+ if image .min () < 0 :
75+ image = (image + 1.0 ) * 127.5
76+ elif image .max () <= 1.0 :
77+ image = image * 255.0
78+
79+ source_height , source_width = image .shape [- 2 :]
80+ scale = max (width / source_width , height / source_height )
81+ resized_height = math .ceil (scale * source_height )
82+ resized_width = math .ceil (scale * source_width )
83+ image = F .interpolate (
84+ image ,
85+ size = (resized_height , resized_width ),
86+ mode = "bilinear" ,
87+ align_corners = False ,
88+ antialias = True ,
89+ )
90+ crop_top = round ((resized_height - height ) / 2 )
91+ crop_left = round ((resized_width - width ) / 2 )
92+ image = image [:, :, crop_top : crop_top + height , crop_left : crop_left + width ]
93+ image = image .round ().clamp (0 , 255 ) / 127.5 - 1.0
94+ return image
95+
96+
5597# ============================================================================
5698# Sequence layout: data structures + builders for the joint token sequence
5799# ============================================================================
@@ -714,7 +756,7 @@ def _remove_action_video_padding_from_latent(
714756
715757 def prepare_latents (
716758 self ,
717- image : torch .Tensor | None = None ,
759+ image : Image . Image | np . ndarray | torch .Tensor | None = None ,
718760 video : list [Image .Image ] | torch .Tensor | np .ndarray | None = None ,
719761 condition_frame_indexes_vision : Iterable [int ] = (0 , 1 ),
720762 condition_video_keep : Literal ["first" , "last" ] = "first" ,
@@ -754,10 +796,9 @@ def prepare_latents(
754796 # Video-to-video conditioning: a top-level `video` without an action run.
755797 has_video_condition = video is not None and action is None
756798
757- # video_processor.preprocess handles PIL/np/tensor → [1, 3, H, W] in [-1, 1], resized to (height, width).
758799 conditioning_frame_2d : torch .Tensor | None = None
759800 if image is not None :
760- conditioning_frame_2d = self . video_processor . preprocess (image , height = height , width = width ).to (
801+ conditioning_frame_2d = _preprocess_conditioning_image (image , height = height , width = width ).to (
761802 device = device , dtype = dtype
762803 )
763804
@@ -1272,7 +1313,7 @@ def __call__(
12721313 self ,
12731314 prompt : str | list [str ],
12741315 negative_prompt : str | list [str ] | None = None ,
1275- image : torch .Tensor | None = None ,
1316+ image : Image . Image | np . ndarray | torch .Tensor | None = None ,
12761317 video : list [Image .Image ] | torch .Tensor | np .ndarray | None = None ,
12771318 condition_frame_indexes_vision : Iterable [int ] = (0 , 1 ),
12781319 condition_video_keep : Literal ["first" , "last" ] = "first" ,
@@ -1314,9 +1355,10 @@ def __call__(
13141355 per call.
13151356 negative_prompt (`str` or `List[str]`, *optional*):
13161357 The negative prompt used for classifier-free guidance. When `None`, the empty string is used.
1317- image (`torch.Tensor` or `PIL.Image.Image `, *optional*):
1358+ image (`PIL.Image.Image`, `np.ndarray`, or `torch.Tensor `, *optional*):
13181359 Optional conditioning frame for image-to-video. The pipeline anchors frame 0 to this image and denoises
1319- the remaining frames. Ignored when `num_frames == 1`. Not used for action runs (pass `action` instead).
1360+ the remaining frames. The image is resized while preserving its aspect ratio, then center-cropped to
1361+ `height` and `width`. Ignored when `num_frames == 1`. Not used for action runs (pass `action` instead).
13201362 Mutually exclusive with `video`.
13211363 video (`List[PIL.Image.Image]`, `torch.Tensor`, or `np.ndarray`, *optional*):
13221364 Optional conditioning clip for video-to-video. The leading frames are kept clean at the latent indexes
0 commit comments