@@ -106,8 +106,10 @@ def prepare_text_for_image_fusion(example, column_name, model_name):
106106 example [column_name ], model_name , processor_output = example ["images" ]
107107 )
108108 if isinstance (example ["images" ], list ):
109+ example ["image_masks" ] = [image .pixel_mask for image in example ["images" ]]
109110 example ["images" ] = [image .pixel_values for image in example ["images" ]]
110111 else :
112+ example ["image_masks" ] = example ["images" ].pixel_mask
111113 example ["images" ] = example ["images" ].pixel_values
112114 return example
113115
@@ -255,6 +257,7 @@ def map(self, element):
255257 "inputs" : np .asarray (inputs [: self .max_target_length ], dtype = np .int32 ),
256258 "targets" : np .asarray (targets [: self .max_target_length ], dtype = np .int32 ),
257259 "images" : element ["images" ],
260+ "image_masks" : element ["image_masks" ],
258261 }
259262
260263
@@ -434,49 +437,77 @@ def _pad_text(self, x, max_length, pad_id):
434437 pad_amount = [(0 , pad_amount )] + [(0 , 0 )] * (len (x .shape ) - 1 )
435438 return np .pad (x , pad_amount , constant_values = pad_id )[: self .max_length ]
436439
437- def _pad_image (self , images ) :
438- """Pads the input images array to match the maximum required number of images per example .
440+ def _pad_image_or_mask (self , tensor : np . ndarray ) -> np . ndarray :
441+ """Pads an input tensor (image or mask) to a maximum number of items .
439442
440- The function computes the maximum number of allowed images either based on model constraints
441- (determined by model name and maximum sequence length) or the value provided in
442- `self.max_num_images_per_example` (if positive), whichever is smaller. If the provided
443- `images` array contains fewer images than the desired maximum, it pads the array with
444- zeros (dummy images) to meet the requirement.
443+ This function unifies padding logic for image tensors (standard or tiled) and
444+ mask tensors. It determines the tensor type based on its dimensions and applies
445+ the appropriate padding along the first axis.
446+
447+ The maximum number of items is calculated based on model constraints or a
448+ user-defined limit, ensuring that sequence length limits are respected while
449+ reserving space for at least one text token. If the input tensor has fewer
450+ items than this maximum, it is padded with zeros.
445451
446452 Args:
447- images (np.ndarray): A numpy array of image data. The expected shape is
448- (num_images, height, width, channels), where num_images <= maximum allowed images.
453+ tensor (np.ndarray): The input numpy array to pad.
454+ - For masks, the expected shape is (num_masks, num_tiles).
455+ - For standard images, the shape is (num_images, H, W, C).
456+ - For tiled images, the shape is (num_images, num_tiles, H, W, C).
449457
450458 Returns:
451- np.ndarray: An array of images padded with zero images (if necessary) such that its
452- first dimension equals the computed maximum number of images .
459+ np.ndarray: The tensor, padded with zeros up to the maximum number of
460+ items along the first axis .
453461
454462 Raises:
455- AssertionError: If the number of images in the input exceeds the allowed maximum.
463+ ValueError: If the input tensor's dimension is not 2, 4, or 5.
464+ ValueError: If the number of items in the input tensor exceeds the
465+ allowed maximum.
456466
457467 Notes:
458468 - The computation of maximum images ensures that space is reserved in the sequence
459469 for at least one text token.
460470 - The dummy images used for padding are based on the image shape for initialization
461471 of this model (ignoring batch size).
462472 """
473+ if not isinstance (tensor , np .ndarray ):
474+ raise TypeError (f"Input must be a numpy array, but got { type (tensor )} " )
475+
476+ # Determine the maximum number of images/masks allowed.
463477 image_offsets = multimodal_utils .get_image_offsets (self .model_name , None )
464- max_num_images = (self .max_length // image_offsets ) - 1 # -1 to reserve space for at least one text token
478+ # Reserve space for at least one text token.
479+ max_num_items = (self .max_length // image_offsets ) - 1
465480 if self .max_num_images_per_example > 0 :
466- max_num_images = min (self .max_num_images_per_example , max_num_images )
467- image_shape = multimodal_utils .get_dummy_image_shape_for_init (self .model_name )[2 :]
468- assert (
469- images .shape [0 ] <= max_num_images
470- ), f"Number of images { images .shape [0 ]} exceeds the maximum allowed { max_num_images } "
471- if images .shape [0 ] < max_num_images :
472- pad_size = max_num_images - images .shape [0 ]
473- pad_shape = (pad_size ,) + image_shape
474- pad_images = np .zeros (pad_shape , dtype = images .dtype )
475- if images is not None and images .size > 0 :
476- images = np .concatenate ([images , pad_images ], axis = 0 )
481+ max_num_items = min (self .max_num_images_per_example , max_num_items )
482+
483+ # Validate tensor dimensions.
484+ if tensor .ndim in (4 , 5 ): # Standard or Tiled Image
485+ tensor_type = "images"
486+ elif tensor .ndim == 2 : # Mask
487+ tensor_type = "masks"
488+ else :
489+ raise ValueError (
490+ "Input tensor must be 2D (mask), 4D (image), or 5D (tiled image), " f"but got { tensor .ndim } dimensions."
491+ )
492+
493+ # Assert that the input tensor does not exceed the maximum size.
494+ if tensor .shape [0 ] > max_num_items :
495+ raise ValueError (f"Number of { tensor_type } ({ tensor .shape [0 ]} ) exceeds the maximum allowed ({ max_num_items } )." )
496+
497+ # Apply padding if the tensor is smaller than the maximum size.
498+ if tensor .shape [0 ] < max_num_items :
499+ pad_size = max_num_items - tensor .shape [0 ]
500+ pad_shape_suffix = tensor .shape [1 :]
501+ pad_shape = (pad_size ,) + pad_shape_suffix
502+ pad_tensor = np .zeros (pad_shape , dtype = tensor .dtype )
503+
504+ if tensor .size > 0 :
505+ tensor = np .concatenate ([tensor , pad_tensor ], axis = 0 )
477506 else :
478- images = pad_images
479- return images
507+ # If the input tensor is empty, the result is just the padding.
508+ tensor = pad_tensor
509+
510+ return tensor
480511
481512 def map (self , element : dict [str , np .ndarray ]):
482513 """map to each element"""
@@ -487,13 +518,27 @@ def map(self, element: dict[str, np.ndarray]):
487518 element [f"{ data_column } _position" ] = np .arange (element [data_column ].shape [0 ], dtype = np .int32 )
488519 if self .add_true_length :
489520 element [f"{ data_column } _true_length" ] = np .array ([element [data_column ].shape [0 ]], dtype = np .int32 )
521+
490522 for key , _ in element .items ():
491523 if key == "images" :
492- if isinstance (element ["images" ], list ):
493- assert self .model_name is not None , "model_name must be provided when padding images"
494- element ["images" ] = self ._pad_image (np .asarray (element ["images" ]))
495- else :
524+ if isinstance (element ["images" ], list ) and self .model_name is None :
525+ raise ValueError ("model_name must be provided when padding images" )
526+ elif isinstance (element ["images" ], list ):
527+ element ["images" ] = self ._pad_image_or_mask (np .asarray (element ["images" ]))
528+ elif element ["images" ].ndim == 3 :
496529 element ["images" ] = np .asarray (element ["images" ])[None , ...]
530+ else :
531+ # Do not add extra image dimension for image tiling case
532+ element ["images" ] = np .asarray (element ["images" ])
533+
534+ elif key == "image_masks" and element ["image_masks" ] is not None :
535+ if isinstance (element ["image_masks" ], list ) and self .model_name is None :
536+ raise ValueError ("model_name must be provided when padding image masks" )
537+ elif isinstance (element ["image_masks" ], list ):
538+ element ["image_masks" ] = self ._pad_image_or_mask (np .asarray (element ["image_masks" ]))
539+ else :
540+ element ["image_masks" ] = np .asarray (element ["image_masks" ])
541+
497542 elif "true_length" not in key :
498543 element [key ] = self ._pad_text (element [key ], self .max_length , self .pad_id )
499544 return element
0 commit comments