Repository: huggingface/transformers
Pull request: Fix temporal padding in Qwen2VLImageProcessor when the number of frames is not divisible by temporal_patch_size

This PR fixes an issue in Qwen2VLImageProcessor where the current implementation does not correctly handle cases when the number of video frames is not divisible by `temporal_patch_size`.

### Problem:
The existing logic repeats the last frame `temporal_patch_size` - 1 times. This works correctly when `temporal_patch_size` equals 2 but fails when the size is greater.

### Solution:
The fix replaces:

```repeats = np.repeat(patches[-1][np.newaxis], temporal_patch_size - 1, axis=0)```
with:

```repeats = np.repeat(patches[-1][np.newaxis], temporal_patch_size - (patches.shape[0] % temporal_patch_size), axis=0)```

This ensures that the correct number of padding frames are added when the frame count is not divisible by the `temporal_patch_size`.

#### Additional Changes:
Added a unit test to verify the padding logic for edge cases where the number of frames is not divisible by the patch size.

#### Issue Reference:
Fixes #38003

Changed files:
filename: src/transformers/models/qwen2_vl/image_processing_qwen2_vl.py
patch: @@ -274,7 +274,9 @@ def _preprocess(
         if data_format == ChannelDimension.LAST:
             patches = patches.transpose(0, 3, 1, 2)
         if patches.shape[0] % temporal_patch_size != 0:
-            repeats = np.repeat(patches[-1][np.newaxis], temporal_patch_size - 1, axis=0)
+            repeats = np.repeat(
+                patches[-1][np.newaxis], temporal_patch_size - (patches.shape[0] % temporal_patch_size), axis=0
+            )
             patches = np.concatenate([patches, repeats], axis=0)
         channel = patches.shape[1]
         grid_t = patches.shape[0] // temporal_patch_size

filename: tests/models/qwen2_vl/test_image_processing_qwen2_vl.py
patch: @@ -312,6 +312,24 @@ def test_custom_image_size(self):
             expected_output_video_shape = [112, 1176]
             self.assertListEqual(list(prcocess_out.pixel_values.shape), expected_output_video_shape)
 
+    def test_temporal_padding(self):
+        for image_processing_class in self.image_processor_list:
+            # Initialize image_processing
+            image_processing = image_processing_class(**self.image_processor_dict)
+            # Create random video inputs with a number of frames not divisible by temporal_patch_size
+            image_processor_tester = Qwen2VLImageProcessingTester(self, num_frames=5, temporal_patch_size=4)
+            video_inputs = image_processor_tester.prepare_video_inputs(equal_resolution=True)
+
+            # Process the video inputs
+            process_out = image_processing(None, videos=video_inputs, return_tensors="pt")
+            encoded_video = process_out.pixel_values_videos
+
+            # Check the shape after padding
+            expected_output_video_shape = (102900, 1176)  # Adjusted based on padding
+            self.assertEqual(tuple(encoded_video.shape), expected_output_video_shape)
+            # Check divisibility by temporal_patch_size
+            self.assertEqual(encoded_video.shape[0] % 4, 0)
+
     @require_vision
     @require_torch
     def test_slow_fast_equivalence(self):