Mask2former & Maskformer Fast Image Processor (#35685)
* add maskformerfast * test * revert do_reduce_labels and add testing * make style & fix-copies * add mask2former and make fix-copies TO DO: add test for mask2former * make fix-copies * fill docstring * enable mask2former fast processor * python utils/custom_init_isort.py * make fix-copies * fix PR's comments * modular file update * add license * make style * modular file * make fix-copies * merge * temp commit * finish up maskformer mask2former * remove zero shot examples --------- Co-authored-by: yonigozlan <yoni.gozlan@huggingface.co> Co-authored-by: Yoni Gozlan <74535834+yonigozlan@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
6e9972962f
commit
d9b35c635e
@@ -77,4 +77,12 @@ The resource should ideally demonstrate something new instead of duplicating an
|
|||||||
- encode_inputs
|
- encode_inputs
|
||||||
- post_process_semantic_segmentation
|
- post_process_semantic_segmentation
|
||||||
- post_process_instance_segmentation
|
- post_process_instance_segmentation
|
||||||
|
- post_process_panoptic_segmentation
|
||||||
|
|
||||||
|
## Mask2FormerImageProcessorFast
|
||||||
|
|
||||||
|
[[autodoc]] Mask2FormerImageProcessorFast
|
||||||
|
- preprocess
|
||||||
|
- post_process_semantic_segmentation
|
||||||
|
- post_process_instance_segmentation
|
||||||
- post_process_panoptic_segmentation
|
- post_process_panoptic_segmentation
|
||||||
@@ -76,6 +76,14 @@ This model was contributed by [francesco](https://huggingface.co/francesco). The
|
|||||||
- post_process_instance_segmentation
|
- post_process_instance_segmentation
|
||||||
- post_process_panoptic_segmentation
|
- post_process_panoptic_segmentation
|
||||||
|
|
||||||
|
## MaskFormerImageProcessorFast
|
||||||
|
|
||||||
|
[[autodoc]] MaskFormerImageProcessorFast
|
||||||
|
- preprocess
|
||||||
|
- post_process_semantic_segmentation
|
||||||
|
- post_process_instance_segmentation
|
||||||
|
- post_process_panoptic_segmentation
|
||||||
|
|
||||||
## MaskFormerFeatureExtractor
|
## MaskFormerFeatureExtractor
|
||||||
|
|
||||||
[[autodoc]] MaskFormerFeatureExtractor
|
[[autodoc]] MaskFormerFeatureExtractor
|
||||||
|
|||||||
@@ -118,8 +118,8 @@ else:
|
|||||||
("llava_next", ("LlavaNextImageProcessor", "LlavaNextImageProcessorFast")),
|
("llava_next", ("LlavaNextImageProcessor", "LlavaNextImageProcessorFast")),
|
||||||
("llava_next_video", ("LlavaNextVideoImageProcessor",)),
|
("llava_next_video", ("LlavaNextVideoImageProcessor",)),
|
||||||
("llava_onevision", ("LlavaOnevisionImageProcessor", "LlavaOnevisionImageProcessorFast")),
|
("llava_onevision", ("LlavaOnevisionImageProcessor", "LlavaOnevisionImageProcessorFast")),
|
||||||
("mask2former", ("Mask2FormerImageProcessor",)),
|
("mask2former", ("Mask2FormerImageProcessor", "Mask2FormerImageProcessorFast")),
|
||||||
("maskformer", ("MaskFormerImageProcessor",)),
|
("maskformer", ("MaskFormerImageProcessor", "MaskFormerImageProcessorFast")),
|
||||||
("mgp-str", ("ViTImageProcessor", "ViTImageProcessorFast")),
|
("mgp-str", ("ViTImageProcessor", "ViTImageProcessorFast")),
|
||||||
("mistral3", ("PixtralImageProcessor", "PixtralImageProcessorFast")),
|
("mistral3", ("PixtralImageProcessor", "PixtralImageProcessorFast")),
|
||||||
("mlcd", ("CLIPImageProcessor", "CLIPImageProcessorFast")),
|
("mlcd", ("CLIPImageProcessor", "CLIPImageProcessorFast")),
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ from ...utils.import_utils import define_import_structure
|
|||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .configuration_mask2former import *
|
from .configuration_mask2former import *
|
||||||
from .image_processing_mask2former import *
|
from .image_processing_mask2former import *
|
||||||
|
from .image_processing_mask2former_fast import *
|
||||||
from .modeling_mask2former import *
|
from .modeling_mask2former import *
|
||||||
else:
|
else:
|
||||||
import sys
|
import sys
|
||||||
|
|||||||
@@ -35,8 +35,8 @@ from ...image_utils import (
|
|||||||
PILImageResampling,
|
PILImageResampling,
|
||||||
get_image_size,
|
get_image_size,
|
||||||
infer_channel_dimension_format,
|
infer_channel_dimension_format,
|
||||||
is_batched,
|
|
||||||
is_scaled_image,
|
is_scaled_image,
|
||||||
|
make_list_of_images,
|
||||||
to_numpy_array,
|
to_numpy_array,
|
||||||
valid_images,
|
valid_images,
|
||||||
validate_preprocess_arguments,
|
validate_preprocess_arguments,
|
||||||
@@ -61,6 +61,46 @@ if is_torch_available():
|
|||||||
from torch import nn
|
from torch import nn
|
||||||
|
|
||||||
|
|
||||||
|
# Copied from transformers.models.detr.image_processing_detr.get_size_with_aspect_ratio
|
||||||
|
def get_size_with_aspect_ratio(image_size, size, max_size=None) -> tuple[int, int]:
|
||||||
|
"""
|
||||||
|
Computes the output image size given the input image size and the desired output size.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
image_size (`tuple[int, int]`):
|
||||||
|
The input image size.
|
||||||
|
size (`int`):
|
||||||
|
The desired output size.
|
||||||
|
max_size (`int`, *optional*):
|
||||||
|
The maximum allowed output size.
|
||||||
|
"""
|
||||||
|
height, width = image_size
|
||||||
|
raw_size = None
|
||||||
|
if max_size is not None:
|
||||||
|
min_original_size = float(min((height, width)))
|
||||||
|
max_original_size = float(max((height, width)))
|
||||||
|
if max_original_size / min_original_size * size > max_size:
|
||||||
|
raw_size = max_size * min_original_size / max_original_size
|
||||||
|
size = int(round(raw_size))
|
||||||
|
|
||||||
|
if (height <= width and height == size) or (width <= height and width == size):
|
||||||
|
oh, ow = height, width
|
||||||
|
elif width < height:
|
||||||
|
ow = size
|
||||||
|
if max_size is not None and raw_size is not None:
|
||||||
|
oh = int(raw_size * height / width)
|
||||||
|
else:
|
||||||
|
oh = int(size * height / width)
|
||||||
|
else:
|
||||||
|
oh = size
|
||||||
|
if max_size is not None and raw_size is not None:
|
||||||
|
ow = int(raw_size * width / height)
|
||||||
|
else:
|
||||||
|
ow = int(size * width / height)
|
||||||
|
|
||||||
|
return (oh, ow)
|
||||||
|
|
||||||
|
|
||||||
# Copied from transformers.models.detr.image_processing_detr.max_across_indices
|
# Copied from transformers.models.detr.image_processing_detr.max_across_indices
|
||||||
def max_across_indices(values: Iterable[Any]) -> list[Any]:
|
def max_across_indices(values: Iterable[Any]) -> list[Any]:
|
||||||
"""
|
"""
|
||||||
@@ -394,6 +434,10 @@ class Mask2FormerImageProcessor(BaseImageProcessor):
|
|||||||
The background label will be replaced by `ignore_index`.
|
The background label will be replaced by `ignore_index`.
|
||||||
num_labels (`int`, *optional*):
|
num_labels (`int`, *optional*):
|
||||||
The number of labels in the segmentation map.
|
The number of labels in the segmentation map.
|
||||||
|
pad_size (`Dict[str, int]`, *optional*):
|
||||||
|
The size `{"height": int, "width" int}` to pad the images to. Must be larger than any image size
|
||||||
|
provided for preprocessing. If `pad_size` is not provided, images will be padded to the largest
|
||||||
|
height and width in the batch.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
model_input_names = ["pixel_values", "pixel_mask"]
|
model_input_names = ["pixel_values", "pixel_mask"]
|
||||||
@@ -416,6 +460,7 @@ class Mask2FormerImageProcessor(BaseImageProcessor):
|
|||||||
ignore_index: Optional[int] = None,
|
ignore_index: Optional[int] = None,
|
||||||
do_reduce_labels: bool = False,
|
do_reduce_labels: bool = False,
|
||||||
num_labels: Optional[int] = None,
|
num_labels: Optional[int] = None,
|
||||||
|
pad_size: Optional[dict[str, int]] = None,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
@@ -439,6 +484,7 @@ class Mask2FormerImageProcessor(BaseImageProcessor):
|
|||||||
self.ignore_index = ignore_index
|
self.ignore_index = ignore_index
|
||||||
self.do_reduce_labels = do_reduce_labels
|
self.do_reduce_labels = do_reduce_labels
|
||||||
self.num_labels = num_labels
|
self.num_labels = num_labels
|
||||||
|
self.pad_size = pad_size
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, image_processor_dict: dict[str, Any], **kwargs):
|
def from_dict(cls, image_processor_dict: dict[str, Any], **kwargs):
|
||||||
@@ -697,6 +743,7 @@ class Mask2FormerImageProcessor(BaseImageProcessor):
|
|||||||
return_tensors: Optional[Union[str, TensorType]] = None,
|
return_tensors: Optional[Union[str, TensorType]] = None,
|
||||||
data_format: Union[str, ChannelDimension] = ChannelDimension.FIRST,
|
data_format: Union[str, ChannelDimension] = ChannelDimension.FIRST,
|
||||||
input_data_format: Optional[Union[str, ChannelDimension]] = None,
|
input_data_format: Optional[Union[str, ChannelDimension]] = None,
|
||||||
|
pad_size: Optional[dict[str, int]] = None,
|
||||||
) -> BatchFeature:
|
) -> BatchFeature:
|
||||||
do_resize = do_resize if do_resize is not None else self.do_resize
|
do_resize = do_resize if do_resize is not None else self.do_resize
|
||||||
size = size if size is not None else self.size
|
size = size if size is not None else self.size
|
||||||
@@ -710,6 +757,7 @@ class Mask2FormerImageProcessor(BaseImageProcessor):
|
|||||||
image_std = image_std if image_std is not None else self.image_std
|
image_std = image_std if image_std is not None else self.image_std
|
||||||
ignore_index = ignore_index if ignore_index is not None else self.ignore_index
|
ignore_index = ignore_index if ignore_index is not None else self.ignore_index
|
||||||
do_reduce_labels = do_reduce_labels if do_reduce_labels is not None else self.do_reduce_labels
|
do_reduce_labels = do_reduce_labels if do_reduce_labels is not None else self.do_reduce_labels
|
||||||
|
pad_size = self.pad_size if pad_size is None else pad_size
|
||||||
|
|
||||||
if not valid_images(images):
|
if not valid_images(images):
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
@@ -734,9 +782,9 @@ class Mask2FormerImageProcessor(BaseImageProcessor):
|
|||||||
"torch.Tensor, tf.Tensor or jax.ndarray."
|
"torch.Tensor, tf.Tensor or jax.ndarray."
|
||||||
)
|
)
|
||||||
|
|
||||||
if not is_batched(images):
|
images = make_list_of_images(images)
|
||||||
images = [images]
|
if segmentation_maps is not None:
|
||||||
segmentation_maps = [segmentation_maps] if segmentation_maps is not None else None
|
segmentation_maps = make_list_of_images(segmentation_maps, expected_ndims=2)
|
||||||
|
|
||||||
if segmentation_maps is not None and len(images) != len(segmentation_maps):
|
if segmentation_maps is not None and len(images) != len(segmentation_maps):
|
||||||
raise ValueError("Images and segmentation maps must have the same length.")
|
raise ValueError("Images and segmentation maps must have the same length.")
|
||||||
@@ -774,6 +822,7 @@ class Mask2FormerImageProcessor(BaseImageProcessor):
|
|||||||
do_reduce_labels,
|
do_reduce_labels,
|
||||||
return_tensors,
|
return_tensors,
|
||||||
input_data_format=data_format,
|
input_data_format=data_format,
|
||||||
|
pad_size=pad_size,
|
||||||
)
|
)
|
||||||
return encoded_inputs
|
return encoded_inputs
|
||||||
|
|
||||||
@@ -805,7 +854,7 @@ class Mask2FormerImageProcessor(BaseImageProcessor):
|
|||||||
)
|
)
|
||||||
return padded_image
|
return padded_image
|
||||||
|
|
||||||
# Copied from transformers.models.vilt.image_processing_vilt.ViltImageProcessor.pad
|
# Copied from transformers.models.maskformer.image_processing_maskformer.MaskFormerImageProcessor.pad
|
||||||
def pad(
|
def pad(
|
||||||
self,
|
self,
|
||||||
images: list[np.ndarray],
|
images: list[np.ndarray],
|
||||||
@@ -814,6 +863,7 @@ class Mask2FormerImageProcessor(BaseImageProcessor):
|
|||||||
return_tensors: Optional[Union[str, TensorType]] = None,
|
return_tensors: Optional[Union[str, TensorType]] = None,
|
||||||
data_format: Optional[ChannelDimension] = None,
|
data_format: Optional[ChannelDimension] = None,
|
||||||
input_data_format: Optional[Union[str, ChannelDimension]] = None,
|
input_data_format: Optional[Union[str, ChannelDimension]] = None,
|
||||||
|
pad_size: Optional[dict[str, int]] = None,
|
||||||
) -> BatchFeature:
|
) -> BatchFeature:
|
||||||
"""
|
"""
|
||||||
Pads a batch of images to the bottom and right of the image with zeros to the size of largest height and width
|
Pads a batch of images to the bottom and right of the image with zeros to the size of largest height and width
|
||||||
@@ -837,13 +887,21 @@ class Mask2FormerImageProcessor(BaseImageProcessor):
|
|||||||
The channel dimension format of the image. If not provided, it will be the same as the input image.
|
The channel dimension format of the image. If not provided, it will be the same as the input image.
|
||||||
input_data_format (`ChannelDimension` or `str`, *optional*):
|
input_data_format (`ChannelDimension` or `str`, *optional*):
|
||||||
The channel dimension format of the input image. If not provided, it will be inferred.
|
The channel dimension format of the input image. If not provided, it will be inferred.
|
||||||
|
pad_size (`Dict[str, int]`, *optional*):
|
||||||
|
The size `{"height": int, "width" int}` to pad the images to. Must be larger than any image size
|
||||||
|
provided for preprocessing. If `pad_size` is not provided, images will be padded to the largest
|
||||||
|
height and width in the batch.
|
||||||
"""
|
"""
|
||||||
pad_size = get_max_height_width(images, input_data_format=input_data_format)
|
pad_size = pad_size if pad_size is not None else self.pad_size
|
||||||
|
if pad_size is not None:
|
||||||
|
padded_size = (pad_size["height"], pad_size["width"])
|
||||||
|
else:
|
||||||
|
padded_size = get_max_height_width(images, input_data_format=input_data_format)
|
||||||
|
|
||||||
padded_images = [
|
padded_images = [
|
||||||
self._pad_image(
|
self._pad_image(
|
||||||
image,
|
image,
|
||||||
pad_size,
|
padded_size,
|
||||||
constant_values=constant_values,
|
constant_values=constant_values,
|
||||||
data_format=data_format,
|
data_format=data_format,
|
||||||
input_data_format=input_data_format,
|
input_data_format=input_data_format,
|
||||||
@@ -854,7 +912,7 @@ class Mask2FormerImageProcessor(BaseImageProcessor):
|
|||||||
|
|
||||||
if return_pixel_mask:
|
if return_pixel_mask:
|
||||||
masks = [
|
masks = [
|
||||||
make_pixel_mask(image=image, output_size=pad_size, input_data_format=input_data_format)
|
make_pixel_mask(image=image, output_size=padded_size, input_data_format=input_data_format)
|
||||||
for image in images
|
for image in images
|
||||||
]
|
]
|
||||||
data["pixel_mask"] = masks
|
data["pixel_mask"] = masks
|
||||||
@@ -870,6 +928,7 @@ class Mask2FormerImageProcessor(BaseImageProcessor):
|
|||||||
do_reduce_labels: bool = False,
|
do_reduce_labels: bool = False,
|
||||||
return_tensors: Optional[Union[str, TensorType]] = None,
|
return_tensors: Optional[Union[str, TensorType]] = None,
|
||||||
input_data_format: Optional[Union[str, ChannelDimension]] = None,
|
input_data_format: Optional[Union[str, ChannelDimension]] = None,
|
||||||
|
pad_size: Optional[dict[str, int]] = None,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Pad images up to the largest image in a batch and create a corresponding `pixel_mask`.
|
Pad images up to the largest image in a batch and create a corresponding `pixel_mask`.
|
||||||
@@ -909,6 +968,11 @@ class Mask2FormerImageProcessor(BaseImageProcessor):
|
|||||||
input_data_format (`ChannelDimension` or `str`, *optional*):
|
input_data_format (`ChannelDimension` or `str`, *optional*):
|
||||||
The channel dimension format of the input image. If not provided, it will be inferred.
|
The channel dimension format of the input image. If not provided, it will be inferred.
|
||||||
|
|
||||||
|
pad_size (`Dict[str, int]`, *optional*):
|
||||||
|
The size `{"height": int, "width" int}` to pad the images to. Must be larger than any image size
|
||||||
|
provided for preprocessing. If `pad_size` is not provided, images will be padded to the largest
|
||||||
|
height and width in the batch.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
[`BatchFeature`]: A [`BatchFeature`] with the following fields:
|
[`BatchFeature`]: A [`BatchFeature`] with the following fields:
|
||||||
|
|
||||||
@@ -930,7 +994,7 @@ class Mask2FormerImageProcessor(BaseImageProcessor):
|
|||||||
input_data_format = infer_channel_dimension_format(pixel_values_list[0])
|
input_data_format = infer_channel_dimension_format(pixel_values_list[0])
|
||||||
|
|
||||||
encoded_inputs = self.pad(
|
encoded_inputs = self.pad(
|
||||||
pixel_values_list, return_tensors=return_tensors, input_data_format=input_data_format
|
pixel_values_list, return_tensors=return_tensors, input_data_format=input_data_format, pad_size=pad_size
|
||||||
)
|
)
|
||||||
|
|
||||||
if segmentation_maps is not None:
|
if segmentation_maps is not None:
|
||||||
|
|||||||
@@ -0,0 +1,737 @@
|
|||||||
|
# 🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨
|
||||||
|
# This file was automatically generated from src/transformers/models/mask2former/modular_mask2former.py.
|
||||||
|
# Do NOT edit this file manually as any edits will be overwritten by the generation of
|
||||||
|
# the file from the modular. If any change should be done, please apply the change to the
|
||||||
|
# modular_mask2former.py file directly. One of our CI enforces this.
|
||||||
|
# 🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨
|
||||||
|
# coding=utf-8
|
||||||
|
# Copyright 2025 The HuggingFace Inc. team.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
import math
|
||||||
|
from typing import Any, Optional, Union
|
||||||
|
|
||||||
|
from ...image_processing_utils import BatchFeature, get_size_dict
|
||||||
|
from ...image_processing_utils_fast import (
|
||||||
|
BaseImageProcessorFast,
|
||||||
|
DefaultFastImageProcessorKwargs,
|
||||||
|
SizeDict,
|
||||||
|
get_image_size_for_max_height_width,
|
||||||
|
get_max_height_width,
|
||||||
|
group_images_by_shape,
|
||||||
|
reorder_images,
|
||||||
|
)
|
||||||
|
from ...image_utils import (
|
||||||
|
IMAGENET_DEFAULT_MEAN,
|
||||||
|
IMAGENET_DEFAULT_STD,
|
||||||
|
ChannelDimension,
|
||||||
|
ImageInput,
|
||||||
|
PILImageResampling,
|
||||||
|
)
|
||||||
|
from ...processing_utils import Unpack
|
||||||
|
from ...utils import (
|
||||||
|
TensorType,
|
||||||
|
auto_docstring,
|
||||||
|
is_torch_available,
|
||||||
|
is_torchvision_available,
|
||||||
|
is_torchvision_v2_available,
|
||||||
|
logging,
|
||||||
|
)
|
||||||
|
from ...utils.deprecation import deprecate_kwarg
|
||||||
|
from .image_processing_mask2former import (
|
||||||
|
compute_segments,
|
||||||
|
convert_segmentation_to_rle,
|
||||||
|
get_size_with_aspect_ratio,
|
||||||
|
remove_low_and_no_objects,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if is_torch_available():
|
||||||
|
import torch
|
||||||
|
from torch import nn
|
||||||
|
|
||||||
|
|
||||||
|
if is_torchvision_v2_available():
|
||||||
|
from torchvision.transforms.v2 import functional as F
|
||||||
|
elif is_torchvision_available():
|
||||||
|
from torchvision.transforms import functional as F
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class Mask2FormerFastImageProcessorKwargs(DefaultFastImageProcessorKwargs):
|
||||||
|
r"""
|
||||||
|
size_divisor (`int`, *optional*, defaults to 32):
|
||||||
|
Some backbones need images divisible by a certain number. If not passed, it defaults to the value used in
|
||||||
|
Swin Transformer.
|
||||||
|
ignore_index (`int`, *optional*):
|
||||||
|
Label to be assigned to background pixels in segmentation maps. If provided, segmentation map pixels
|
||||||
|
denoted with 0 (background) will be replaced with `ignore_index`.
|
||||||
|
do_reduce_labels (`bool`, *optional*, defaults to `False`):
|
||||||
|
Whether or not to decrement all label values of segmentation maps by 1. Usually used for datasets where 0
|
||||||
|
is used for background, and background itself is not included in all classes of a dataset (e.g. ADE20k).
|
||||||
|
The background label will be replaced by `ignore_index`.
|
||||||
|
num_labels (`int`, *optional*):
|
||||||
|
The number of labels in the segmentation map.
|
||||||
|
do_pad (`bool`, *optional*, defaults to `True`):
|
||||||
|
Controls whether to pad the image. Can be overridden by the `do_pad` parameter in the `preprocess`
|
||||||
|
method. If `True`, padding will be applied to the bottom and right of the image with zeros.
|
||||||
|
If `pad_size` is provided, the image will be padded to the specified dimensions.
|
||||||
|
Otherwise, the image will be padded to the maximum height and width of the batch.
|
||||||
|
pad_size (`Dict[str, int]`, *optional*):
|
||||||
|
The size `{"height": int, "width" int}` to pad the images to. Must be larger than any image size
|
||||||
|
provided for preprocessing. If `pad_size` is not provided, images will be padded to the largest
|
||||||
|
height and width in the batch.
|
||||||
|
"""
|
||||||
|
|
||||||
|
size_divisor: Optional[int]
|
||||||
|
ignore_index: Optional[int]
|
||||||
|
do_reduce_labels: Optional[bool]
|
||||||
|
num_labels: Optional[int]
|
||||||
|
do_pad: Optional[bool]
|
||||||
|
pad_size: Optional[dict[str, int]]
|
||||||
|
|
||||||
|
|
||||||
|
def convert_segmentation_map_to_binary_masks_fast(
|
||||||
|
segmentation_map: "torch.Tensor",
|
||||||
|
instance_id_to_semantic_id: Optional[dict[int, int]] = None,
|
||||||
|
ignore_index: Optional[int] = None,
|
||||||
|
do_reduce_labels: bool = False,
|
||||||
|
):
|
||||||
|
if do_reduce_labels and ignore_index is None:
|
||||||
|
raise ValueError("If `do_reduce_labels` is True, `ignore_index` must be provided.")
|
||||||
|
|
||||||
|
if do_reduce_labels:
|
||||||
|
segmentation_map = torch.where(segmentation_map == 0, ignore_index, segmentation_map - 1)
|
||||||
|
|
||||||
|
all_labels = torch.unique(segmentation_map)
|
||||||
|
|
||||||
|
if ignore_index is not None:
|
||||||
|
all_labels = all_labels[all_labels != ignore_index] # drop background label if applicable
|
||||||
|
|
||||||
|
binary_masks = [(segmentation_map == i) for i in all_labels]
|
||||||
|
if binary_masks:
|
||||||
|
binary_masks = torch.stack(binary_masks, dim=0)
|
||||||
|
else:
|
||||||
|
binary_masks = torch.zeros((0, *segmentation_map.shape), device=segmentation_map.device)
|
||||||
|
|
||||||
|
# Convert instance ids to class ids
|
||||||
|
if instance_id_to_semantic_id is not None:
|
||||||
|
labels = torch.zeros(all_labels.shape[0], device=segmentation_map.device)
|
||||||
|
|
||||||
|
for i, label in enumerate(all_labels):
|
||||||
|
class_id = instance_id_to_semantic_id[(label.item() + 1 if do_reduce_labels else label.item())]
|
||||||
|
labels[i] = class_id - 1 if do_reduce_labels else class_id
|
||||||
|
else:
|
||||||
|
labels = all_labels
|
||||||
|
return binary_masks.float(), labels.long()
|
||||||
|
|
||||||
|
|
||||||
|
@auto_docstring
|
||||||
|
class Mask2FormerImageProcessorFast(BaseImageProcessorFast):
|
||||||
|
resample = PILImageResampling.BILINEAR
|
||||||
|
image_mean = IMAGENET_DEFAULT_MEAN
|
||||||
|
image_std = IMAGENET_DEFAULT_STD
|
||||||
|
size = {"shortest_edge": 800, "longest_edge": 1333}
|
||||||
|
default_to_square = False
|
||||||
|
do_resize = True
|
||||||
|
do_rescale = True
|
||||||
|
rescale_factor = 1 / 255
|
||||||
|
do_normalize = True
|
||||||
|
do_pad = True
|
||||||
|
model_input_names = ["pixel_values", "pixel_mask"]
|
||||||
|
size_divisor = 32
|
||||||
|
do_reduce_labels = False
|
||||||
|
valid_kwargs = Mask2FormerFastImageProcessorKwargs
|
||||||
|
|
||||||
|
@deprecate_kwarg("reduce_labels", new_name="do_reduce_labels", version="4.44.0")
|
||||||
|
@deprecate_kwarg("size_divisibility", new_name="size_divisor", version="4.41.0")
|
||||||
|
@deprecate_kwarg("max_size", version="4.27.0", warn_if_greater_or_equal_version=True)
|
||||||
|
def __init__(self, **kwargs: Unpack[Mask2FormerFastImageProcessorKwargs]) -> None:
|
||||||
|
if "pad_and_return_pixel_mask" in kwargs:
|
||||||
|
kwargs["do_pad"] = kwargs.pop("pad_and_return_pixel_mask")
|
||||||
|
|
||||||
|
size = kwargs.pop("size", None)
|
||||||
|
max_size = kwargs.pop("max_size", None)
|
||||||
|
|
||||||
|
if size is None and max_size is not None:
|
||||||
|
size = self.size
|
||||||
|
size["longest_edge"] = max_size
|
||||||
|
elif size is None:
|
||||||
|
size = self.size
|
||||||
|
|
||||||
|
self.size = get_size_dict(size, max_size=max_size, default_to_square=False)
|
||||||
|
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_dict(cls, image_processor_dict: dict[str, Any], **kwargs):
|
||||||
|
"""
|
||||||
|
Overrides the `from_dict` method from the base class to make sure parameters are updated if image processor is
|
||||||
|
created using from_dict and kwargs e.g. `Mask2FormerImageProcessor.from_pretrained(checkpoint, max_size=800)`
|
||||||
|
"""
|
||||||
|
image_processor_dict = image_processor_dict.copy()
|
||||||
|
if "max_size" in kwargs:
|
||||||
|
image_processor_dict["max_size"] = kwargs.pop("max_size")
|
||||||
|
if "size_divisibility" in kwargs:
|
||||||
|
image_processor_dict["size_divisor"] = kwargs.pop("size_divisibility")
|
||||||
|
if "reduce_labels" in image_processor_dict:
|
||||||
|
image_processor_dict["do_reduce_labels"] = image_processor_dict.pop("reduce_labels")
|
||||||
|
return super().from_dict(image_processor_dict, **kwargs)
|
||||||
|
|
||||||
|
def to_dict(self) -> dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Serializes this instance to a Python dictionary. This method calls the superclass method and then removes the
|
||||||
|
`_max_size` attribute from the dictionary.
|
||||||
|
"""
|
||||||
|
image_processor_dict = super().to_dict()
|
||||||
|
image_processor_dict.pop("_max_size", None)
|
||||||
|
return image_processor_dict
|
||||||
|
|
||||||
|
def reduce_label(self, labels: list["torch.Tensor"]):
|
||||||
|
for idx in range(len(labels)):
|
||||||
|
label = labels[idx]
|
||||||
|
label = torch.where(label == 0, torch.tensor(255, dtype=label.dtype), label)
|
||||||
|
label = label - 1
|
||||||
|
label = torch.where(label == 254, torch.tensor(255, dtype=label.dtype), label)
|
||||||
|
labels[idx] = label
|
||||||
|
|
||||||
|
def resize(
|
||||||
|
self,
|
||||||
|
image: torch.Tensor,
|
||||||
|
size: SizeDict,
|
||||||
|
size_divisor: int = 0,
|
||||||
|
interpolation: "F.InterpolationMode" = None,
|
||||||
|
**kwargs,
|
||||||
|
) -> torch.Tensor:
|
||||||
|
"""
|
||||||
|
Resize the image to the given size. Size can be `min_size` (scalar) or `(height, width)` tuple. If size is an
|
||||||
|
int, smaller edge of the image will be matched to this number.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
image (`torch.Tensor`):
|
||||||
|
Image to resize.
|
||||||
|
size (`SizeDict`):
|
||||||
|
Size of the image's `(height, width)` dimensions after resizing. Available options are:
|
||||||
|
- `{"height": int, "width": int}`: The image will be resized to the exact size `(height, width)`.
|
||||||
|
Do NOT keep the aspect ratio.
|
||||||
|
- `{"shortest_edge": int, "longest_edge": int}`: The image will be resized to a maximum size respecting
|
||||||
|
the aspect ratio and keeping the shortest edge less or equal to `shortest_edge` and the longest edge
|
||||||
|
less or equal to `longest_edge`.
|
||||||
|
- `{"max_height": int, "max_width": int}`: The image will be resized to the maximum size respecting the
|
||||||
|
aspect ratio and keeping the height less or equal to `max_height` and the width less or equal to
|
||||||
|
`max_width`.
|
||||||
|
size_divisor (`int`, *optional*, defaults to 0):
|
||||||
|
If `size_divisor` is given, the output image size will be divisible by the number.
|
||||||
|
interpolation (`InterpolationMode`, *optional*, defaults to `InterpolationMode.BILINEAR`):
|
||||||
|
Resampling filter to use if resizing the image.
|
||||||
|
"""
|
||||||
|
interpolation = interpolation if interpolation is not None else F.InterpolationMode.BILINEAR
|
||||||
|
if size.shortest_edge and size.longest_edge:
|
||||||
|
# Resize the image so that the shortest edge or the longest edge is of the given size
|
||||||
|
# while maintaining the aspect ratio of the original image.
|
||||||
|
new_size = get_size_with_aspect_ratio(
|
||||||
|
image.size()[-2:],
|
||||||
|
size["shortest_edge"],
|
||||||
|
size["longest_edge"],
|
||||||
|
)
|
||||||
|
elif size.max_height and size.max_width:
|
||||||
|
new_size = get_image_size_for_max_height_width(image.size()[-2:], size["max_height"], size["max_width"])
|
||||||
|
elif size.height and size.width:
|
||||||
|
new_size = (size["height"], size["width"])
|
||||||
|
else:
|
||||||
|
raise ValueError(
|
||||||
|
"Size must contain 'height' and 'width' keys or 'shortest_edge' and 'longest_edge' keys. Got"
|
||||||
|
f" {size.keys()}."
|
||||||
|
)
|
||||||
|
if size_divisor > 0:
|
||||||
|
height, width = new_size
|
||||||
|
height = int(math.ceil(height / size_divisor) * size_divisor)
|
||||||
|
width = int(math.ceil(width / size_divisor) * size_divisor)
|
||||||
|
new_size = (height, width)
|
||||||
|
|
||||||
|
image = F.resize(
|
||||||
|
image,
|
||||||
|
size=new_size,
|
||||||
|
interpolation=interpolation,
|
||||||
|
**kwargs,
|
||||||
|
)
|
||||||
|
return image
|
||||||
|
|
||||||
|
def pad(
|
||||||
|
self,
|
||||||
|
images: torch.Tensor,
|
||||||
|
padded_size: tuple[int, int],
|
||||||
|
segmentation_maps: Optional[torch.Tensor] = None,
|
||||||
|
fill: int = 0,
|
||||||
|
ignore_index: int = 255,
|
||||||
|
) -> BatchFeature:
|
||||||
|
original_size = images.size()[-2:]
|
||||||
|
padding_bottom = padded_size[0] - original_size[0]
|
||||||
|
padding_right = padded_size[1] - original_size[1]
|
||||||
|
if padding_bottom < 0 or padding_right < 0:
|
||||||
|
raise ValueError(
|
||||||
|
f"Padding dimensions are negative. Please make sure that the padded size is larger than the "
|
||||||
|
f"original size. Got padded size: {padded_size}, original size: {original_size}."
|
||||||
|
)
|
||||||
|
if original_size != padded_size:
|
||||||
|
padding = [0, 0, padding_right, padding_bottom]
|
||||||
|
images = F.pad(images, padding, fill=fill)
|
||||||
|
if segmentation_maps is not None:
|
||||||
|
segmentation_maps = F.pad(segmentation_maps, padding, fill=ignore_index)
|
||||||
|
|
||||||
|
# Make a pixel mask for the image, where 1 indicates a valid pixel and 0 indicates padding.
|
||||||
|
pixel_mask = torch.zeros((images.shape[0], *padded_size), dtype=torch.int64, device=images.device)
|
||||||
|
pixel_mask[:, : original_size[0], : original_size[1]] = 1
|
||||||
|
|
||||||
|
return images, pixel_mask, segmentation_maps
|
||||||
|
|
||||||
|
@auto_docstring
|
||||||
|
def preprocess(
|
||||||
|
self,
|
||||||
|
images: ImageInput,
|
||||||
|
segmentation_maps: Optional[ImageInput] = None,
|
||||||
|
instance_id_to_semantic_id: Optional[Union[list[dict[int, int]], dict[int, int]]] = None,
|
||||||
|
**kwargs: Unpack[Mask2FormerFastImageProcessorKwargs],
|
||||||
|
) -> BatchFeature:
|
||||||
|
r"""
|
||||||
|
segmentation_maps (`ImageInput`, *optional*):
|
||||||
|
The segmentation maps.
|
||||||
|
instance_id_to_semantic_id (`Union[list[dict[int, int]], dict[int, int]]`, *optional*):
|
||||||
|
A mapping from instance IDs to semantic IDs.
|
||||||
|
"""
|
||||||
|
return super().preprocess(
|
||||||
|
images,
|
||||||
|
segmentation_maps,
|
||||||
|
instance_id_to_semantic_id,
|
||||||
|
**kwargs,
|
||||||
|
)
|
||||||
|
|
||||||
|
def _preprocess_image_like_inputs(
|
||||||
|
self,
|
||||||
|
images: ImageInput,
|
||||||
|
segmentation_maps: ImageInput,
|
||||||
|
instance_id_to_semantic_id: Optional[Union[list[dict[int, int]], dict[int, int]]],
|
||||||
|
do_convert_rgb: bool,
|
||||||
|
input_data_format: ChannelDimension,
|
||||||
|
device: Optional[Union[str, "torch.device"]] = None,
|
||||||
|
**kwargs: Unpack[Mask2FormerFastImageProcessorKwargs],
|
||||||
|
) -> BatchFeature:
|
||||||
|
"""
|
||||||
|
Preprocess image-like inputs.
|
||||||
|
To be overriden by subclasses when image-like inputs other than images should be processed.
|
||||||
|
It can be used for segmentation maps, depth maps, etc.
|
||||||
|
"""
|
||||||
|
# Prepare input images
|
||||||
|
images = self._prepare_image_like_inputs(
|
||||||
|
images=images, do_convert_rgb=do_convert_rgb, input_data_format=input_data_format, device=device
|
||||||
|
)
|
||||||
|
if segmentation_maps is not None:
|
||||||
|
segmentation_maps = self._prepare_image_like_inputs(
|
||||||
|
images=segmentation_maps,
|
||||||
|
expected_ndims=2,
|
||||||
|
do_convert_rgb=False,
|
||||||
|
input_data_format=ChannelDimension.FIRST,
|
||||||
|
)
|
||||||
|
return self._preprocess(images, segmentation_maps, instance_id_to_semantic_id, **kwargs)
|
||||||
|
|
||||||
|
def _preprocess(
|
||||||
|
self,
|
||||||
|
images: list["torch.Tensor"],
|
||||||
|
segmentation_maps: Optional["torch.Tensor"],
|
||||||
|
instance_id_to_semantic_id: Optional[dict[int, int]],
|
||||||
|
do_resize: Optional[bool],
|
||||||
|
size: Optional[dict[str, int]],
|
||||||
|
pad_size: Optional[dict[str, int]],
|
||||||
|
size_divisor: Optional[int],
|
||||||
|
interpolation: Optional[Union["PILImageResampling", "F.InterpolationMode"]],
|
||||||
|
do_rescale: Optional[bool],
|
||||||
|
rescale_factor: Optional[float],
|
||||||
|
do_normalize: Optional[bool],
|
||||||
|
image_mean: Optional[Union[float, list[float]]],
|
||||||
|
image_std: Optional[Union[float, list[float]]],
|
||||||
|
ignore_index: Optional[int],
|
||||||
|
do_reduce_labels: Optional[bool],
|
||||||
|
disable_grouping: Optional[bool],
|
||||||
|
return_tensors: Optional[Union[str, TensorType]],
|
||||||
|
**kwargs,
|
||||||
|
) -> BatchFeature:
|
||||||
|
if segmentation_maps is not None and len(images) != len(segmentation_maps):
|
||||||
|
raise ValueError("Images and segmentation maps must have the same length.")
|
||||||
|
|
||||||
|
# Group images by size for batched resizing
|
||||||
|
grouped_images, grouped_images_index = group_images_by_shape(images, disable_grouping=disable_grouping)
|
||||||
|
resized_images_grouped = {}
|
||||||
|
if segmentation_maps is not None:
|
||||||
|
grouped_segmentation_maps, grouped_segmentation_maps_index = group_images_by_shape(
|
||||||
|
segmentation_maps, disable_grouping=disable_grouping
|
||||||
|
)
|
||||||
|
resized_segmentation_maps_grouped = {}
|
||||||
|
for shape, stacked_images in grouped_images.items():
|
||||||
|
if do_resize:
|
||||||
|
stacked_images = self.resize(
|
||||||
|
image=stacked_images, size=size, size_divisor=size_divisor, interpolation=interpolation
|
||||||
|
)
|
||||||
|
if segmentation_maps is not None:
|
||||||
|
stacked_segmentation_maps = self.resize(
|
||||||
|
image=grouped_segmentation_maps[shape],
|
||||||
|
size=size,
|
||||||
|
size_divisor=size_divisor,
|
||||||
|
interpolation=F.InterpolationMode.NEAREST_EXACT,
|
||||||
|
)
|
||||||
|
resized_images_grouped[shape] = stacked_images
|
||||||
|
if segmentation_maps is not None:
|
||||||
|
resized_segmentation_maps_grouped[shape] = stacked_segmentation_maps
|
||||||
|
resized_images = reorder_images(resized_images_grouped, grouped_images_index)
|
||||||
|
if segmentation_maps is not None:
|
||||||
|
resized_segmentation_maps = reorder_images(
|
||||||
|
resized_segmentation_maps_grouped, grouped_segmentation_maps_index
|
||||||
|
)
|
||||||
|
if pad_size is not None:
|
||||||
|
padded_size = (pad_size["height"], pad_size["width"])
|
||||||
|
else:
|
||||||
|
padded_size = get_max_height_width(resized_images)
|
||||||
|
|
||||||
|
if segmentation_maps is not None:
|
||||||
|
mask_labels = []
|
||||||
|
class_labels = []
|
||||||
|
# Convert to list of binary masks and labels
|
||||||
|
for idx, segmentation_map in enumerate(resized_segmentation_maps):
|
||||||
|
if isinstance(instance_id_to_semantic_id, list):
|
||||||
|
instance_id = instance_id_to_semantic_id[idx]
|
||||||
|
else:
|
||||||
|
instance_id = instance_id_to_semantic_id
|
||||||
|
# Use instance2class_id mapping per image
|
||||||
|
masks, classes = convert_segmentation_map_to_binary_masks_fast(
|
||||||
|
segmentation_map.squeeze(0),
|
||||||
|
instance_id,
|
||||||
|
ignore_index=ignore_index,
|
||||||
|
do_reduce_labels=do_reduce_labels,
|
||||||
|
)
|
||||||
|
mask_labels.append(masks)
|
||||||
|
class_labels.append(classes)
|
||||||
|
|
||||||
|
grouped_images, grouped_images_index = group_images_by_shape(resized_images, disable_grouping=disable_grouping)
|
||||||
|
processed_images_grouped = {}
|
||||||
|
processed_pixel_masks_grouped = {}
|
||||||
|
if segmentation_maps is not None:
|
||||||
|
grouped_segmentation_maps, grouped_segmentation_maps_index = group_images_by_shape(
|
||||||
|
mask_labels, disable_grouping=disable_grouping
|
||||||
|
)
|
||||||
|
processed_segmentation_maps_grouped = {}
|
||||||
|
for shape, stacked_images in grouped_images.items():
|
||||||
|
# Fused rescale and normalize
|
||||||
|
stacked_images = self.rescale_and_normalize(
|
||||||
|
stacked_images, do_rescale, rescale_factor, do_normalize, image_mean, image_std
|
||||||
|
)
|
||||||
|
padded_images, pixel_masks, padded_segmentation_maps = self.pad(
|
||||||
|
images=stacked_images,
|
||||||
|
segmentation_maps=grouped_segmentation_maps[shape] if segmentation_maps is not None else None,
|
||||||
|
padded_size=padded_size,
|
||||||
|
ignore_index=ignore_index,
|
||||||
|
)
|
||||||
|
processed_images_grouped[shape] = padded_images
|
||||||
|
processed_pixel_masks_grouped[shape] = pixel_masks
|
||||||
|
if segmentation_maps is not None:
|
||||||
|
processed_segmentation_maps_grouped[shape] = padded_segmentation_maps.squeeze(1)
|
||||||
|
processed_images = reorder_images(processed_images_grouped, grouped_images_index)
|
||||||
|
processed_pixel_masks = reorder_images(processed_pixel_masks_grouped, grouped_images_index)
|
||||||
|
encoded_inputs = BatchFeature(
|
||||||
|
data={
|
||||||
|
"pixel_values": torch.stack(processed_images, dim=0) if return_tensors else processed_images,
|
||||||
|
"pixel_mask": torch.stack(processed_pixel_masks, dim=0) if return_tensors else processed_pixel_masks,
|
||||||
|
},
|
||||||
|
tensor_type=return_tensors,
|
||||||
|
)
|
||||||
|
if segmentation_maps is not None:
|
||||||
|
mask_labels = reorder_images(processed_segmentation_maps_grouped, grouped_segmentation_maps_index)
|
||||||
|
# we cannot batch them since they don't share a common class size
|
||||||
|
encoded_inputs["mask_labels"] = mask_labels
|
||||||
|
encoded_inputs["class_labels"] = class_labels
|
||||||
|
|
||||||
|
return encoded_inputs
|
||||||
|
|
||||||
|
def post_process_semantic_segmentation(
|
||||||
|
self, outputs, target_sizes: Optional[list[tuple[int, int]]] = None
|
||||||
|
) -> "torch.Tensor":
|
||||||
|
"""
|
||||||
|
Converts the output of [`Mask2FormerForUniversalSegmentation`] into semantic segmentation maps. Only supports
|
||||||
|
PyTorch.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
outputs ([`Mask2FormerForUniversalSegmentation`]):
|
||||||
|
Raw outputs of the model.
|
||||||
|
target_sizes (`List[Tuple[int, int]]`, *optional*):
|
||||||
|
List of length (batch_size), where each list item (`Tuple[int, int]]`) corresponds to the requested
|
||||||
|
final size (height, width) of each prediction. If left to None, predictions will not be resized.
|
||||||
|
Returns:
|
||||||
|
`List[torch.Tensor]`:
|
||||||
|
A list of length `batch_size`, where each item is a semantic segmentation map of shape (height, width)
|
||||||
|
corresponding to the target_sizes entry (if `target_sizes` is specified). Each entry of each
|
||||||
|
`torch.Tensor` correspond to a semantic class id.
|
||||||
|
"""
|
||||||
|
class_queries_logits = outputs.class_queries_logits # [batch_size, num_queries, num_classes+1]
|
||||||
|
masks_queries_logits = outputs.masks_queries_logits # [batch_size, num_queries, height, width]
|
||||||
|
|
||||||
|
# Scale back to preprocessed image size - (384, 384) for all models
|
||||||
|
masks_queries_logits = torch.nn.functional.interpolate(
|
||||||
|
masks_queries_logits, size=(384, 384), mode="bilinear", align_corners=False
|
||||||
|
)
|
||||||
|
|
||||||
|
# Remove the null class `[..., :-1]`
|
||||||
|
masks_classes = class_queries_logits.softmax(dim=-1)[..., :-1]
|
||||||
|
masks_probs = masks_queries_logits.sigmoid() # [batch_size, num_queries, height, width]
|
||||||
|
|
||||||
|
# Semantic segmentation logits of shape (batch_size, num_classes, height, width)
|
||||||
|
segmentation = torch.einsum("bqc, bqhw -> bchw", masks_classes, masks_probs)
|
||||||
|
batch_size = class_queries_logits.shape[0]
|
||||||
|
|
||||||
|
# Resize logits and compute semantic segmentation maps
|
||||||
|
if target_sizes is not None:
|
||||||
|
if batch_size != len(target_sizes):
|
||||||
|
raise ValueError(
|
||||||
|
"Make sure that you pass in as many target sizes as the batch dimension of the logits"
|
||||||
|
)
|
||||||
|
|
||||||
|
semantic_segmentation = []
|
||||||
|
for idx in range(batch_size):
|
||||||
|
resized_logits = torch.nn.functional.interpolate(
|
||||||
|
segmentation[idx].unsqueeze(dim=0), size=target_sizes[idx], mode="bilinear", align_corners=False
|
||||||
|
)
|
||||||
|
semantic_map = resized_logits[0].argmax(dim=0)
|
||||||
|
semantic_segmentation.append(semantic_map)
|
||||||
|
else:
|
||||||
|
semantic_segmentation = segmentation.argmax(dim=1)
|
||||||
|
semantic_segmentation = [semantic_segmentation[i] for i in range(semantic_segmentation.shape[0])]
|
||||||
|
|
||||||
|
return semantic_segmentation
|
||||||
|
|
||||||
|
def post_process_instance_segmentation(
|
||||||
|
self,
|
||||||
|
outputs,
|
||||||
|
threshold: float = 0.5,
|
||||||
|
mask_threshold: float = 0.5,
|
||||||
|
overlap_mask_area_threshold: float = 0.8,
|
||||||
|
target_sizes: Optional[list[tuple[int, int]]] = None,
|
||||||
|
return_coco_annotation: Optional[bool] = False,
|
||||||
|
return_binary_maps: Optional[bool] = False,
|
||||||
|
) -> list[dict]:
|
||||||
|
"""
|
||||||
|
Converts the output of [`Mask2FormerForUniversalSegmentationOutput`] into instance segmentation predictions.
|
||||||
|
Only supports PyTorch. If instances could overlap, set either return_coco_annotation or return_binary_maps
|
||||||
|
to `True` to get the correct segmentation result.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
outputs ([`Mask2FormerForUniversalSegmentation`]):
|
||||||
|
Raw outputs of the model.
|
||||||
|
threshold (`float`, *optional*, defaults to 0.5):
|
||||||
|
The probability score threshold to keep predicted instance masks.
|
||||||
|
mask_threshold (`float`, *optional*, defaults to 0.5):
|
||||||
|
Threshold to use when turning the predicted masks into binary values.
|
||||||
|
overlap_mask_area_threshold (`float`, *optional*, defaults to 0.8):
|
||||||
|
The overlap mask area threshold to merge or discard small disconnected parts within each binary
|
||||||
|
instance mask.
|
||||||
|
target_sizes (`List[Tuple]`, *optional*):
|
||||||
|
List of length (batch_size), where each list item (`Tuple[int, int]]`) corresponds to the requested
|
||||||
|
final size (height, width) of each prediction. If left to None, predictions will not be resized.
|
||||||
|
return_coco_annotation (`bool`, *optional*, defaults to `False`):
|
||||||
|
If set to `True`, segmentation maps are returned in COCO run-length encoding (RLE) format.
|
||||||
|
return_binary_maps (`bool`, *optional*, defaults to `False`):
|
||||||
|
If set to `True`, segmentation maps are returned as a concatenated tensor of binary segmentation maps
|
||||||
|
(one per detected instance).
|
||||||
|
Returns:
|
||||||
|
`List[Dict]`: A list of dictionaries, one per image, each dictionary containing two keys:
|
||||||
|
- **segmentation** -- A tensor of shape `(height, width)` where each pixel represents a `segment_id`, or
|
||||||
|
`List[List]` run-length encoding (RLE) of the segmentation map if return_coco_annotation is set to
|
||||||
|
`True`, or a tensor of shape `(num_instances, height, width)` if return_binary_maps is set to `True`.
|
||||||
|
Set to `None` if no mask if found above `threshold`.
|
||||||
|
- **segments_info** -- A dictionary that contains additional information on each segment.
|
||||||
|
- **id** -- An integer representing the `segment_id`.
|
||||||
|
- **label_id** -- An integer representing the label / semantic class id corresponding to `segment_id`.
|
||||||
|
- **score** -- Prediction score of segment with `segment_id`.
|
||||||
|
"""
|
||||||
|
if return_coco_annotation and return_binary_maps:
|
||||||
|
raise ValueError("return_coco_annotation and return_binary_maps can not be both set to True.")
|
||||||
|
|
||||||
|
# [batch_size, num_queries, num_classes+1]
|
||||||
|
class_queries_logits = outputs.class_queries_logits
|
||||||
|
# [batch_size, num_queries, height, width]
|
||||||
|
masks_queries_logits = outputs.masks_queries_logits
|
||||||
|
|
||||||
|
# Scale back to preprocessed image size - (384, 384) for all models
|
||||||
|
masks_queries_logits = torch.nn.functional.interpolate(
|
||||||
|
masks_queries_logits, size=(384, 384), mode="bilinear", align_corners=False
|
||||||
|
)
|
||||||
|
|
||||||
|
device = masks_queries_logits.device
|
||||||
|
num_classes = class_queries_logits.shape[-1] - 1
|
||||||
|
num_queries = class_queries_logits.shape[-2]
|
||||||
|
|
||||||
|
# Loop over items in batch size
|
||||||
|
results: list[dict[str, TensorType]] = []
|
||||||
|
|
||||||
|
for i in range(class_queries_logits.shape[0]):
|
||||||
|
mask_pred = masks_queries_logits[i]
|
||||||
|
mask_cls = class_queries_logits[i]
|
||||||
|
|
||||||
|
scores = torch.nn.functional.softmax(mask_cls, dim=-1)[:, :-1]
|
||||||
|
labels = torch.arange(num_classes, device=device).unsqueeze(0).repeat(num_queries, 1).flatten(0, 1)
|
||||||
|
|
||||||
|
scores_per_image, topk_indices = scores.flatten(0, 1).topk(num_queries, sorted=False)
|
||||||
|
labels_per_image = labels[topk_indices]
|
||||||
|
|
||||||
|
topk_indices = torch.div(topk_indices, num_classes, rounding_mode="floor")
|
||||||
|
mask_pred = mask_pred[topk_indices]
|
||||||
|
pred_masks = (mask_pred > 0).float()
|
||||||
|
|
||||||
|
# Calculate average mask prob
|
||||||
|
mask_scores_per_image = (mask_pred.sigmoid().flatten(1) * pred_masks.flatten(1)).sum(1) / (
|
||||||
|
pred_masks.flatten(1).sum(1) + 1e-6
|
||||||
|
)
|
||||||
|
pred_scores = scores_per_image * mask_scores_per_image
|
||||||
|
pred_classes = labels_per_image
|
||||||
|
|
||||||
|
segmentation = torch.zeros((384, 384)) - 1
|
||||||
|
if target_sizes is not None:
|
||||||
|
segmentation = torch.zeros(target_sizes[i]) - 1
|
||||||
|
pred_masks = torch.nn.functional.interpolate(
|
||||||
|
pred_masks.unsqueeze(0), size=target_sizes[i], mode="nearest"
|
||||||
|
)[0]
|
||||||
|
|
||||||
|
instance_maps, segments = [], []
|
||||||
|
current_segment_id = 0
|
||||||
|
for j in range(num_queries):
|
||||||
|
score = pred_scores[j].item()
|
||||||
|
|
||||||
|
if not torch.all(pred_masks[j] == 0) and score >= threshold:
|
||||||
|
segmentation[pred_masks[j] == 1] = current_segment_id
|
||||||
|
segments.append(
|
||||||
|
{
|
||||||
|
"id": current_segment_id,
|
||||||
|
"label_id": pred_classes[j].item(),
|
||||||
|
"was_fused": False,
|
||||||
|
"score": round(score, 6),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
current_segment_id += 1
|
||||||
|
instance_maps.append(pred_masks[j])
|
||||||
|
|
||||||
|
# Return segmentation map in run-length encoding (RLE) format
|
||||||
|
if return_coco_annotation:
|
||||||
|
segmentation = convert_segmentation_to_rle(segmentation)
|
||||||
|
|
||||||
|
# Return a concatenated tensor of binary instance maps
|
||||||
|
if return_binary_maps and len(instance_maps) != 0:
|
||||||
|
segmentation = torch.stack(instance_maps, dim=0)
|
||||||
|
|
||||||
|
results.append({"segmentation": segmentation, "segments_info": segments})
|
||||||
|
return results
|
||||||
|
|
||||||
|
def post_process_panoptic_segmentation(
|
||||||
|
self,
|
||||||
|
outputs,
|
||||||
|
threshold: float = 0.5,
|
||||||
|
mask_threshold: float = 0.5,
|
||||||
|
overlap_mask_area_threshold: float = 0.8,
|
||||||
|
label_ids_to_fuse: Optional[set[int]] = None,
|
||||||
|
target_sizes: Optional[list[tuple[int, int]]] = None,
|
||||||
|
) -> list[dict]:
|
||||||
|
"""
|
||||||
|
Converts the output of [`Mask2FormerForUniversalSegmentationOutput`] into image panoptic segmentation
|
||||||
|
predictions. Only supports PyTorch.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
outputs ([`Mask2FormerForUniversalSegmentationOutput`]):
|
||||||
|
The outputs from [`Mask2FormerForUniversalSegmentation`].
|
||||||
|
threshold (`float`, *optional*, defaults to 0.5):
|
||||||
|
The probability score threshold to keep predicted instance masks.
|
||||||
|
mask_threshold (`float`, *optional*, defaults to 0.5):
|
||||||
|
Threshold to use when turning the predicted masks into binary values.
|
||||||
|
overlap_mask_area_threshold (`float`, *optional*, defaults to 0.8):
|
||||||
|
The overlap mask area threshold to merge or discard small disconnected parts within each binary
|
||||||
|
instance mask.
|
||||||
|
label_ids_to_fuse (`Set[int]`, *optional*):
|
||||||
|
The labels in this state will have all their instances be fused together. For instance we could say
|
||||||
|
there can only be one sky in an image, but several persons, so the label ID for sky would be in that
|
||||||
|
set, but not the one for person.
|
||||||
|
target_sizes (`List[Tuple]`, *optional*):
|
||||||
|
List of length (batch_size), where each list item (`Tuple[int, int]]`) corresponds to the requested
|
||||||
|
final size (height, width) of each prediction in batch. If left to None, predictions will not be
|
||||||
|
resized.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
`List[Dict]`: A list of dictionaries, one per image, each dictionary containing two keys:
|
||||||
|
- **segmentation** -- a tensor of shape `(height, width)` where each pixel represents a `segment_id`, set
|
||||||
|
to `None` if no mask if found above `threshold`. If `target_sizes` is specified, segmentation is resized
|
||||||
|
to the corresponding `target_sizes` entry.
|
||||||
|
- **segments_info** -- A dictionary that contains additional information on each segment.
|
||||||
|
- **id** -- an integer representing the `segment_id`.
|
||||||
|
- **label_id** -- An integer representing the label / semantic class id corresponding to `segment_id`.
|
||||||
|
- **was_fused** -- a boolean, `True` if `label_id` was in `label_ids_to_fuse`, `False` otherwise.
|
||||||
|
Multiple instances of the same class / label were fused and assigned a single `segment_id`.
|
||||||
|
- **score** -- Prediction score of segment with `segment_id`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if label_ids_to_fuse is None:
|
||||||
|
logger.warning("`label_ids_to_fuse` unset. No instance will be fused.")
|
||||||
|
label_ids_to_fuse = set()
|
||||||
|
|
||||||
|
class_queries_logits = outputs.class_queries_logits # [batch_size, num_queries, num_classes+1]
|
||||||
|
masks_queries_logits = outputs.masks_queries_logits # [batch_size, num_queries, height, width]
|
||||||
|
|
||||||
|
# Scale back to preprocessed image size - (384, 384) for all models
|
||||||
|
masks_queries_logits = torch.nn.functional.interpolate(
|
||||||
|
masks_queries_logits, size=(384, 384), mode="bilinear", align_corners=False
|
||||||
|
)
|
||||||
|
|
||||||
|
batch_size = class_queries_logits.shape[0]
|
||||||
|
num_labels = class_queries_logits.shape[-1] - 1
|
||||||
|
|
||||||
|
mask_probs = masks_queries_logits.sigmoid() # [batch_size, num_queries, height, width]
|
||||||
|
|
||||||
|
# Predicted label and score of each query (batch_size, num_queries)
|
||||||
|
pred_scores, pred_labels = nn.functional.softmax(class_queries_logits, dim=-1).max(-1)
|
||||||
|
|
||||||
|
# Loop over items in batch size
|
||||||
|
results: list[dict[str, TensorType]] = []
|
||||||
|
|
||||||
|
for i in range(batch_size):
|
||||||
|
mask_probs_item, pred_scores_item, pred_labels_item = remove_low_and_no_objects(
|
||||||
|
mask_probs[i], pred_scores[i], pred_labels[i], threshold, num_labels
|
||||||
|
)
|
||||||
|
|
||||||
|
# No mask found
|
||||||
|
if mask_probs_item.shape[0] <= 0:
|
||||||
|
height, width = target_sizes[i] if target_sizes is not None else mask_probs_item.shape[1:]
|
||||||
|
segmentation = torch.zeros((height, width)) - 1
|
||||||
|
results.append({"segmentation": segmentation, "segments_info": []})
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Get segmentation map and segment information of batch item
|
||||||
|
target_size = target_sizes[i] if target_sizes is not None else None
|
||||||
|
segmentation, segments = compute_segments(
|
||||||
|
mask_probs=mask_probs_item,
|
||||||
|
pred_scores=pred_scores_item,
|
||||||
|
pred_labels=pred_labels_item,
|
||||||
|
mask_threshold=mask_threshold,
|
||||||
|
overlap_mask_area_threshold=overlap_mask_area_threshold,
|
||||||
|
label_ids_to_fuse=label_ids_to_fuse,
|
||||||
|
target_size=target_size,
|
||||||
|
)
|
||||||
|
|
||||||
|
results.append({"segmentation": segmentation, "segments_info": segments})
|
||||||
|
return results
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = ["Mask2FormerImageProcessorFast"]
|
||||||
315
src/transformers/models/mask2former/modular_mask2former.py
Normal file
315
src/transformers/models/mask2former/modular_mask2former.py
Normal file
@@ -0,0 +1,315 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
# Copyright 2025 The HuggingFace Inc. team.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from transformers.models.maskformer.image_processing_maskformer_fast import MaskFormerImageProcessorFast
|
||||||
|
|
||||||
|
from ...utils import (
|
||||||
|
TensorType,
|
||||||
|
is_torch_available,
|
||||||
|
logging,
|
||||||
|
)
|
||||||
|
from .image_processing_mask2former import (
|
||||||
|
compute_segments,
|
||||||
|
convert_segmentation_to_rle,
|
||||||
|
remove_low_and_no_objects,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if is_torch_available():
|
||||||
|
import torch
|
||||||
|
from torch import nn
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class Mask2FormerImageProcessorFast(MaskFormerImageProcessorFast):
|
||||||
|
def post_process_semantic_segmentation(
|
||||||
|
self, outputs, target_sizes: Optional[list[tuple[int, int]]] = None
|
||||||
|
) -> "torch.Tensor":
|
||||||
|
"""
|
||||||
|
Converts the output of [`Mask2FormerForUniversalSegmentation`] into semantic segmentation maps. Only supports
|
||||||
|
PyTorch.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
outputs ([`Mask2FormerForUniversalSegmentation`]):
|
||||||
|
Raw outputs of the model.
|
||||||
|
target_sizes (`List[Tuple[int, int]]`, *optional*):
|
||||||
|
List of length (batch_size), where each list item (`Tuple[int, int]]`) corresponds to the requested
|
||||||
|
final size (height, width) of each prediction. If left to None, predictions will not be resized.
|
||||||
|
Returns:
|
||||||
|
`List[torch.Tensor]`:
|
||||||
|
A list of length `batch_size`, where each item is a semantic segmentation map of shape (height, width)
|
||||||
|
corresponding to the target_sizes entry (if `target_sizes` is specified). Each entry of each
|
||||||
|
`torch.Tensor` correspond to a semantic class id.
|
||||||
|
"""
|
||||||
|
class_queries_logits = outputs.class_queries_logits # [batch_size, num_queries, num_classes+1]
|
||||||
|
masks_queries_logits = outputs.masks_queries_logits # [batch_size, num_queries, height, width]
|
||||||
|
|
||||||
|
# Scale back to preprocessed image size - (384, 384) for all models
|
||||||
|
masks_queries_logits = torch.nn.functional.interpolate(
|
||||||
|
masks_queries_logits, size=(384, 384), mode="bilinear", align_corners=False
|
||||||
|
)
|
||||||
|
|
||||||
|
# Remove the null class `[..., :-1]`
|
||||||
|
masks_classes = class_queries_logits.softmax(dim=-1)[..., :-1]
|
||||||
|
masks_probs = masks_queries_logits.sigmoid() # [batch_size, num_queries, height, width]
|
||||||
|
|
||||||
|
# Semantic segmentation logits of shape (batch_size, num_classes, height, width)
|
||||||
|
segmentation = torch.einsum("bqc, bqhw -> bchw", masks_classes, masks_probs)
|
||||||
|
batch_size = class_queries_logits.shape[0]
|
||||||
|
|
||||||
|
# Resize logits and compute semantic segmentation maps
|
||||||
|
if target_sizes is not None:
|
||||||
|
if batch_size != len(target_sizes):
|
||||||
|
raise ValueError(
|
||||||
|
"Make sure that you pass in as many target sizes as the batch dimension of the logits"
|
||||||
|
)
|
||||||
|
|
||||||
|
semantic_segmentation = []
|
||||||
|
for idx in range(batch_size):
|
||||||
|
resized_logits = torch.nn.functional.interpolate(
|
||||||
|
segmentation[idx].unsqueeze(dim=0), size=target_sizes[idx], mode="bilinear", align_corners=False
|
||||||
|
)
|
||||||
|
semantic_map = resized_logits[0].argmax(dim=0)
|
||||||
|
semantic_segmentation.append(semantic_map)
|
||||||
|
else:
|
||||||
|
semantic_segmentation = segmentation.argmax(dim=1)
|
||||||
|
semantic_segmentation = [semantic_segmentation[i] for i in range(semantic_segmentation.shape[0])]
|
||||||
|
|
||||||
|
return semantic_segmentation
|
||||||
|
|
||||||
|
def post_process_instance_segmentation(
|
||||||
|
self,
|
||||||
|
outputs,
|
||||||
|
threshold: float = 0.5,
|
||||||
|
mask_threshold: float = 0.5,
|
||||||
|
overlap_mask_area_threshold: float = 0.8,
|
||||||
|
target_sizes: Optional[list[tuple[int, int]]] = None,
|
||||||
|
return_coco_annotation: Optional[bool] = False,
|
||||||
|
return_binary_maps: Optional[bool] = False,
|
||||||
|
) -> list[dict]:
|
||||||
|
"""
|
||||||
|
Converts the output of [`Mask2FormerForUniversalSegmentationOutput`] into instance segmentation predictions.
|
||||||
|
Only supports PyTorch. If instances could overlap, set either return_coco_annotation or return_binary_maps
|
||||||
|
to `True` to get the correct segmentation result.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
outputs ([`Mask2FormerForUniversalSegmentation`]):
|
||||||
|
Raw outputs of the model.
|
||||||
|
threshold (`float`, *optional*, defaults to 0.5):
|
||||||
|
The probability score threshold to keep predicted instance masks.
|
||||||
|
mask_threshold (`float`, *optional*, defaults to 0.5):
|
||||||
|
Threshold to use when turning the predicted masks into binary values.
|
||||||
|
overlap_mask_area_threshold (`float`, *optional*, defaults to 0.8):
|
||||||
|
The overlap mask area threshold to merge or discard small disconnected parts within each binary
|
||||||
|
instance mask.
|
||||||
|
target_sizes (`List[Tuple]`, *optional*):
|
||||||
|
List of length (batch_size), where each list item (`Tuple[int, int]]`) corresponds to the requested
|
||||||
|
final size (height, width) of each prediction. If left to None, predictions will not be resized.
|
||||||
|
return_coco_annotation (`bool`, *optional*, defaults to `False`):
|
||||||
|
If set to `True`, segmentation maps are returned in COCO run-length encoding (RLE) format.
|
||||||
|
return_binary_maps (`bool`, *optional*, defaults to `False`):
|
||||||
|
If set to `True`, segmentation maps are returned as a concatenated tensor of binary segmentation maps
|
||||||
|
(one per detected instance).
|
||||||
|
Returns:
|
||||||
|
`List[Dict]`: A list of dictionaries, one per image, each dictionary containing two keys:
|
||||||
|
- **segmentation** -- A tensor of shape `(height, width)` where each pixel represents a `segment_id`, or
|
||||||
|
`List[List]` run-length encoding (RLE) of the segmentation map if return_coco_annotation is set to
|
||||||
|
`True`, or a tensor of shape `(num_instances, height, width)` if return_binary_maps is set to `True`.
|
||||||
|
Set to `None` if no mask if found above `threshold`.
|
||||||
|
- **segments_info** -- A dictionary that contains additional information on each segment.
|
||||||
|
- **id** -- An integer representing the `segment_id`.
|
||||||
|
- **label_id** -- An integer representing the label / semantic class id corresponding to `segment_id`.
|
||||||
|
- **score** -- Prediction score of segment with `segment_id`.
|
||||||
|
"""
|
||||||
|
if return_coco_annotation and return_binary_maps:
|
||||||
|
raise ValueError("return_coco_annotation and return_binary_maps can not be both set to True.")
|
||||||
|
|
||||||
|
# [batch_size, num_queries, num_classes+1]
|
||||||
|
class_queries_logits = outputs.class_queries_logits
|
||||||
|
# [batch_size, num_queries, height, width]
|
||||||
|
masks_queries_logits = outputs.masks_queries_logits
|
||||||
|
|
||||||
|
# Scale back to preprocessed image size - (384, 384) for all models
|
||||||
|
masks_queries_logits = torch.nn.functional.interpolate(
|
||||||
|
masks_queries_logits, size=(384, 384), mode="bilinear", align_corners=False
|
||||||
|
)
|
||||||
|
|
||||||
|
device = masks_queries_logits.device
|
||||||
|
num_classes = class_queries_logits.shape[-1] - 1
|
||||||
|
num_queries = class_queries_logits.shape[-2]
|
||||||
|
|
||||||
|
# Loop over items in batch size
|
||||||
|
results: list[dict[str, TensorType]] = []
|
||||||
|
|
||||||
|
for i in range(class_queries_logits.shape[0]):
|
||||||
|
mask_pred = masks_queries_logits[i]
|
||||||
|
mask_cls = class_queries_logits[i]
|
||||||
|
|
||||||
|
scores = torch.nn.functional.softmax(mask_cls, dim=-1)[:, :-1]
|
||||||
|
labels = torch.arange(num_classes, device=device).unsqueeze(0).repeat(num_queries, 1).flatten(0, 1)
|
||||||
|
|
||||||
|
scores_per_image, topk_indices = scores.flatten(0, 1).topk(num_queries, sorted=False)
|
||||||
|
labels_per_image = labels[topk_indices]
|
||||||
|
|
||||||
|
topk_indices = torch.div(topk_indices, num_classes, rounding_mode="floor")
|
||||||
|
mask_pred = mask_pred[topk_indices]
|
||||||
|
pred_masks = (mask_pred > 0).float()
|
||||||
|
|
||||||
|
# Calculate average mask prob
|
||||||
|
mask_scores_per_image = (mask_pred.sigmoid().flatten(1) * pred_masks.flatten(1)).sum(1) / (
|
||||||
|
pred_masks.flatten(1).sum(1) + 1e-6
|
||||||
|
)
|
||||||
|
pred_scores = scores_per_image * mask_scores_per_image
|
||||||
|
pred_classes = labels_per_image
|
||||||
|
|
||||||
|
segmentation = torch.zeros((384, 384)) - 1
|
||||||
|
if target_sizes is not None:
|
||||||
|
segmentation = torch.zeros(target_sizes[i]) - 1
|
||||||
|
pred_masks = torch.nn.functional.interpolate(
|
||||||
|
pred_masks.unsqueeze(0), size=target_sizes[i], mode="nearest"
|
||||||
|
)[0]
|
||||||
|
|
||||||
|
instance_maps, segments = [], []
|
||||||
|
current_segment_id = 0
|
||||||
|
for j in range(num_queries):
|
||||||
|
score = pred_scores[j].item()
|
||||||
|
|
||||||
|
if not torch.all(pred_masks[j] == 0) and score >= threshold:
|
||||||
|
segmentation[pred_masks[j] == 1] = current_segment_id
|
||||||
|
segments.append(
|
||||||
|
{
|
||||||
|
"id": current_segment_id,
|
||||||
|
"label_id": pred_classes[j].item(),
|
||||||
|
"was_fused": False,
|
||||||
|
"score": round(score, 6),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
current_segment_id += 1
|
||||||
|
instance_maps.append(pred_masks[j])
|
||||||
|
|
||||||
|
# Return segmentation map in run-length encoding (RLE) format
|
||||||
|
if return_coco_annotation:
|
||||||
|
segmentation = convert_segmentation_to_rle(segmentation)
|
||||||
|
|
||||||
|
# Return a concatenated tensor of binary instance maps
|
||||||
|
if return_binary_maps and len(instance_maps) != 0:
|
||||||
|
segmentation = torch.stack(instance_maps, dim=0)
|
||||||
|
|
||||||
|
results.append({"segmentation": segmentation, "segments_info": segments})
|
||||||
|
return results
|
||||||
|
|
||||||
|
def post_process_panoptic_segmentation(
|
||||||
|
self,
|
||||||
|
outputs,
|
||||||
|
threshold: float = 0.5,
|
||||||
|
mask_threshold: float = 0.5,
|
||||||
|
overlap_mask_area_threshold: float = 0.8,
|
||||||
|
label_ids_to_fuse: Optional[set[int]] = None,
|
||||||
|
target_sizes: Optional[list[tuple[int, int]]] = None,
|
||||||
|
) -> list[dict]:
|
||||||
|
"""
|
||||||
|
Converts the output of [`Mask2FormerForUniversalSegmentationOutput`] into image panoptic segmentation
|
||||||
|
predictions. Only supports PyTorch.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
outputs ([`Mask2FormerForUniversalSegmentationOutput`]):
|
||||||
|
The outputs from [`Mask2FormerForUniversalSegmentation`].
|
||||||
|
threshold (`float`, *optional*, defaults to 0.5):
|
||||||
|
The probability score threshold to keep predicted instance masks.
|
||||||
|
mask_threshold (`float`, *optional*, defaults to 0.5):
|
||||||
|
Threshold to use when turning the predicted masks into binary values.
|
||||||
|
overlap_mask_area_threshold (`float`, *optional*, defaults to 0.8):
|
||||||
|
The overlap mask area threshold to merge or discard small disconnected parts within each binary
|
||||||
|
instance mask.
|
||||||
|
label_ids_to_fuse (`Set[int]`, *optional*):
|
||||||
|
The labels in this state will have all their instances be fused together. For instance we could say
|
||||||
|
there can only be one sky in an image, but several persons, so the label ID for sky would be in that
|
||||||
|
set, but not the one for person.
|
||||||
|
target_sizes (`List[Tuple]`, *optional*):
|
||||||
|
List of length (batch_size), where each list item (`Tuple[int, int]]`) corresponds to the requested
|
||||||
|
final size (height, width) of each prediction in batch. If left to None, predictions will not be
|
||||||
|
resized.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
`List[Dict]`: A list of dictionaries, one per image, each dictionary containing two keys:
|
||||||
|
- **segmentation** -- a tensor of shape `(height, width)` where each pixel represents a `segment_id`, set
|
||||||
|
to `None` if no mask if found above `threshold`. If `target_sizes` is specified, segmentation is resized
|
||||||
|
to the corresponding `target_sizes` entry.
|
||||||
|
- **segments_info** -- A dictionary that contains additional information on each segment.
|
||||||
|
- **id** -- an integer representing the `segment_id`.
|
||||||
|
- **label_id** -- An integer representing the label / semantic class id corresponding to `segment_id`.
|
||||||
|
- **was_fused** -- a boolean, `True` if `label_id` was in `label_ids_to_fuse`, `False` otherwise.
|
||||||
|
Multiple instances of the same class / label were fused and assigned a single `segment_id`.
|
||||||
|
- **score** -- Prediction score of segment with `segment_id`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if label_ids_to_fuse is None:
|
||||||
|
logger.warning("`label_ids_to_fuse` unset. No instance will be fused.")
|
||||||
|
label_ids_to_fuse = set()
|
||||||
|
|
||||||
|
class_queries_logits = outputs.class_queries_logits # [batch_size, num_queries, num_classes+1]
|
||||||
|
masks_queries_logits = outputs.masks_queries_logits # [batch_size, num_queries, height, width]
|
||||||
|
|
||||||
|
# Scale back to preprocessed image size - (384, 384) for all models
|
||||||
|
masks_queries_logits = torch.nn.functional.interpolate(
|
||||||
|
masks_queries_logits, size=(384, 384), mode="bilinear", align_corners=False
|
||||||
|
)
|
||||||
|
|
||||||
|
batch_size = class_queries_logits.shape[0]
|
||||||
|
num_labels = class_queries_logits.shape[-1] - 1
|
||||||
|
|
||||||
|
mask_probs = masks_queries_logits.sigmoid() # [batch_size, num_queries, height, width]
|
||||||
|
|
||||||
|
# Predicted label and score of each query (batch_size, num_queries)
|
||||||
|
pred_scores, pred_labels = nn.functional.softmax(class_queries_logits, dim=-1).max(-1)
|
||||||
|
|
||||||
|
# Loop over items in batch size
|
||||||
|
results: list[dict[str, TensorType]] = []
|
||||||
|
|
||||||
|
for i in range(batch_size):
|
||||||
|
mask_probs_item, pred_scores_item, pred_labels_item = remove_low_and_no_objects(
|
||||||
|
mask_probs[i], pred_scores[i], pred_labels[i], threshold, num_labels
|
||||||
|
)
|
||||||
|
|
||||||
|
# No mask found
|
||||||
|
if mask_probs_item.shape[0] <= 0:
|
||||||
|
height, width = target_sizes[i] if target_sizes is not None else mask_probs_item.shape[1:]
|
||||||
|
segmentation = torch.zeros((height, width)) - 1
|
||||||
|
results.append({"segmentation": segmentation, "segments_info": []})
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Get segmentation map and segment information of batch item
|
||||||
|
target_size = target_sizes[i] if target_sizes is not None else None
|
||||||
|
segmentation, segments = compute_segments(
|
||||||
|
mask_probs=mask_probs_item,
|
||||||
|
pred_scores=pred_scores_item,
|
||||||
|
pred_labels=pred_labels_item,
|
||||||
|
mask_threshold=mask_threshold,
|
||||||
|
overlap_mask_area_threshold=overlap_mask_area_threshold,
|
||||||
|
label_ids_to_fuse=label_ids_to_fuse,
|
||||||
|
target_size=target_size,
|
||||||
|
)
|
||||||
|
|
||||||
|
results.append({"segmentation": segmentation, "segments_info": segments})
|
||||||
|
return results
|
||||||
|
|
||||||
|
def post_process_segmentation():
|
||||||
|
raise NotImplementedError("Segmentation post-processing is not implemented for Mask2Former yet.")
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = ["Mask2FormerImageProcessorFast"]
|
||||||
@@ -22,6 +22,7 @@ if TYPE_CHECKING:
|
|||||||
from .configuration_maskformer_swin import *
|
from .configuration_maskformer_swin import *
|
||||||
from .feature_extraction_maskformer import *
|
from .feature_extraction_maskformer import *
|
||||||
from .image_processing_maskformer import *
|
from .image_processing_maskformer import *
|
||||||
|
from .image_processing_maskformer_fast import *
|
||||||
from .modeling_maskformer import *
|
from .modeling_maskformer import *
|
||||||
from .modeling_maskformer_swin import *
|
from .modeling_maskformer_swin import *
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -67,6 +67,46 @@ if is_torch_available():
|
|||||||
from torch import nn
|
from torch import nn
|
||||||
|
|
||||||
|
|
||||||
|
# Copied from transformers.models.detr.image_processing_detr.get_size_with_aspect_ratio
|
||||||
|
def get_size_with_aspect_ratio(image_size, size, max_size=None) -> tuple[int, int]:
|
||||||
|
"""
|
||||||
|
Computes the output image size given the input image size and the desired output size.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
image_size (`tuple[int, int]`):
|
||||||
|
The input image size.
|
||||||
|
size (`int`):
|
||||||
|
The desired output size.
|
||||||
|
max_size (`int`, *optional*):
|
||||||
|
The maximum allowed output size.
|
||||||
|
"""
|
||||||
|
height, width = image_size
|
||||||
|
raw_size = None
|
||||||
|
if max_size is not None:
|
||||||
|
min_original_size = float(min((height, width)))
|
||||||
|
max_original_size = float(max((height, width)))
|
||||||
|
if max_original_size / min_original_size * size > max_size:
|
||||||
|
raw_size = max_size * min_original_size / max_original_size
|
||||||
|
size = int(round(raw_size))
|
||||||
|
|
||||||
|
if (height <= width and height == size) or (width <= height and width == size):
|
||||||
|
oh, ow = height, width
|
||||||
|
elif width < height:
|
||||||
|
ow = size
|
||||||
|
if max_size is not None and raw_size is not None:
|
||||||
|
oh = int(raw_size * height / width)
|
||||||
|
else:
|
||||||
|
oh = int(size * height / width)
|
||||||
|
else:
|
||||||
|
oh = size
|
||||||
|
if max_size is not None and raw_size is not None:
|
||||||
|
ow = int(raw_size * width / height)
|
||||||
|
else:
|
||||||
|
ow = int(size * width / height)
|
||||||
|
|
||||||
|
return (oh, ow)
|
||||||
|
|
||||||
|
|
||||||
# Copied from transformers.models.detr.image_processing_detr.max_across_indices
|
# Copied from transformers.models.detr.image_processing_detr.max_across_indices
|
||||||
def max_across_indices(values: Iterable[Any]) -> list[Any]:
|
def max_across_indices(values: Iterable[Any]) -> list[Any]:
|
||||||
"""
|
"""
|
||||||
@@ -399,6 +439,10 @@ class MaskFormerImageProcessor(BaseImageProcessor):
|
|||||||
The background label will be replaced by `ignore_index`.
|
The background label will be replaced by `ignore_index`.
|
||||||
num_labels (`int`, *optional*):
|
num_labels (`int`, *optional*):
|
||||||
The number of labels in the segmentation map.
|
The number of labels in the segmentation map.
|
||||||
|
pad_size (`Dict[str, int]`, *optional*):
|
||||||
|
The size `{"height": int, "width" int}` to pad the images to. Must be larger than any image size
|
||||||
|
provided for preprocessing. If `pad_size` is not provided, images will be padded to the largest
|
||||||
|
height and width in the batch.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -422,6 +466,7 @@ class MaskFormerImageProcessor(BaseImageProcessor):
|
|||||||
ignore_index: Optional[int] = None,
|
ignore_index: Optional[int] = None,
|
||||||
do_reduce_labels: bool = False,
|
do_reduce_labels: bool = False,
|
||||||
num_labels: Optional[int] = None,
|
num_labels: Optional[int] = None,
|
||||||
|
pad_size: Optional[dict[str, int]] = None,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
@@ -445,6 +490,7 @@ class MaskFormerImageProcessor(BaseImageProcessor):
|
|||||||
self.ignore_index = ignore_index
|
self.ignore_index = ignore_index
|
||||||
self.do_reduce_labels = do_reduce_labels
|
self.do_reduce_labels = do_reduce_labels
|
||||||
self.num_labels = num_labels
|
self.num_labels = num_labels
|
||||||
|
self.pad_size = pad_size
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, image_processor_dict: dict[str, Any], **kwargs):
|
def from_dict(cls, image_processor_dict: dict[str, Any], **kwargs):
|
||||||
@@ -700,6 +746,7 @@ class MaskFormerImageProcessor(BaseImageProcessor):
|
|||||||
return_tensors: Optional[Union[str, TensorType]] = None,
|
return_tensors: Optional[Union[str, TensorType]] = None,
|
||||||
data_format: Union[str, ChannelDimension] = ChannelDimension.FIRST,
|
data_format: Union[str, ChannelDimension] = ChannelDimension.FIRST,
|
||||||
input_data_format: Optional[Union[str, ChannelDimension]] = None,
|
input_data_format: Optional[Union[str, ChannelDimension]] = None,
|
||||||
|
pad_size: Optional[dict[str, int]] = None,
|
||||||
) -> BatchFeature:
|
) -> BatchFeature:
|
||||||
do_resize = do_resize if do_resize is not None else self.do_resize
|
do_resize = do_resize if do_resize is not None else self.do_resize
|
||||||
size = size if size is not None else self.size
|
size = size if size is not None else self.size
|
||||||
@@ -713,6 +760,7 @@ class MaskFormerImageProcessor(BaseImageProcessor):
|
|||||||
image_std = image_std if image_std is not None else self.image_std
|
image_std = image_std if image_std is not None else self.image_std
|
||||||
ignore_index = ignore_index if ignore_index is not None else self.ignore_index
|
ignore_index = ignore_index if ignore_index is not None else self.ignore_index
|
||||||
do_reduce_labels = do_reduce_labels if do_reduce_labels is not None else self.do_reduce_labels
|
do_reduce_labels = do_reduce_labels if do_reduce_labels is not None else self.do_reduce_labels
|
||||||
|
pad_size = self.pad_size if pad_size is None else pad_size
|
||||||
|
|
||||||
if not valid_images(images):
|
if not valid_images(images):
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
@@ -777,6 +825,7 @@ class MaskFormerImageProcessor(BaseImageProcessor):
|
|||||||
do_reduce_labels,
|
do_reduce_labels,
|
||||||
return_tensors,
|
return_tensors,
|
||||||
input_data_format=data_format,
|
input_data_format=data_format,
|
||||||
|
pad_size=pad_size,
|
||||||
)
|
)
|
||||||
return encoded_inputs
|
return encoded_inputs
|
||||||
|
|
||||||
@@ -808,7 +857,6 @@ class MaskFormerImageProcessor(BaseImageProcessor):
|
|||||||
)
|
)
|
||||||
return padded_image
|
return padded_image
|
||||||
|
|
||||||
# Copied from transformers.models.vilt.image_processing_vilt.ViltImageProcessor.pad
|
|
||||||
def pad(
|
def pad(
|
||||||
self,
|
self,
|
||||||
images: list[np.ndarray],
|
images: list[np.ndarray],
|
||||||
@@ -817,6 +865,7 @@ class MaskFormerImageProcessor(BaseImageProcessor):
|
|||||||
return_tensors: Optional[Union[str, TensorType]] = None,
|
return_tensors: Optional[Union[str, TensorType]] = None,
|
||||||
data_format: Optional[ChannelDimension] = None,
|
data_format: Optional[ChannelDimension] = None,
|
||||||
input_data_format: Optional[Union[str, ChannelDimension]] = None,
|
input_data_format: Optional[Union[str, ChannelDimension]] = None,
|
||||||
|
pad_size: Optional[dict[str, int]] = None,
|
||||||
) -> BatchFeature:
|
) -> BatchFeature:
|
||||||
"""
|
"""
|
||||||
Pads a batch of images to the bottom and right of the image with zeros to the size of largest height and width
|
Pads a batch of images to the bottom and right of the image with zeros to the size of largest height and width
|
||||||
@@ -840,13 +889,21 @@ class MaskFormerImageProcessor(BaseImageProcessor):
|
|||||||
The channel dimension format of the image. If not provided, it will be the same as the input image.
|
The channel dimension format of the image. If not provided, it will be the same as the input image.
|
||||||
input_data_format (`ChannelDimension` or `str`, *optional*):
|
input_data_format (`ChannelDimension` or `str`, *optional*):
|
||||||
The channel dimension format of the input image. If not provided, it will be inferred.
|
The channel dimension format of the input image. If not provided, it will be inferred.
|
||||||
|
pad_size (`Dict[str, int]`, *optional*):
|
||||||
|
The size `{"height": int, "width" int}` to pad the images to. Must be larger than any image size
|
||||||
|
provided for preprocessing. If `pad_size` is not provided, images will be padded to the largest
|
||||||
|
height and width in the batch.
|
||||||
"""
|
"""
|
||||||
pad_size = get_max_height_width(images, input_data_format=input_data_format)
|
pad_size = pad_size if pad_size is not None else self.pad_size
|
||||||
|
if pad_size is not None:
|
||||||
|
padded_size = (pad_size["height"], pad_size["width"])
|
||||||
|
else:
|
||||||
|
padded_size = get_max_height_width(images, input_data_format=input_data_format)
|
||||||
|
|
||||||
padded_images = [
|
padded_images = [
|
||||||
self._pad_image(
|
self._pad_image(
|
||||||
image,
|
image,
|
||||||
pad_size,
|
padded_size,
|
||||||
constant_values=constant_values,
|
constant_values=constant_values,
|
||||||
data_format=data_format,
|
data_format=data_format,
|
||||||
input_data_format=input_data_format,
|
input_data_format=input_data_format,
|
||||||
@@ -857,7 +914,7 @@ class MaskFormerImageProcessor(BaseImageProcessor):
|
|||||||
|
|
||||||
if return_pixel_mask:
|
if return_pixel_mask:
|
||||||
masks = [
|
masks = [
|
||||||
make_pixel_mask(image=image, output_size=pad_size, input_data_format=input_data_format)
|
make_pixel_mask(image=image, output_size=padded_size, input_data_format=input_data_format)
|
||||||
for image in images
|
for image in images
|
||||||
]
|
]
|
||||||
data["pixel_mask"] = masks
|
data["pixel_mask"] = masks
|
||||||
@@ -873,6 +930,7 @@ class MaskFormerImageProcessor(BaseImageProcessor):
|
|||||||
do_reduce_labels: bool = False,
|
do_reduce_labels: bool = False,
|
||||||
return_tensors: Optional[Union[str, TensorType]] = None,
|
return_tensors: Optional[Union[str, TensorType]] = None,
|
||||||
input_data_format: Optional[Union[str, ChannelDimension]] = None,
|
input_data_format: Optional[Union[str, ChannelDimension]] = None,
|
||||||
|
pad_size: Optional[dict[str, int]] = None,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Pad images up to the largest image in a batch and create a corresponding `pixel_mask`.
|
Pad images up to the largest image in a batch and create a corresponding `pixel_mask`.
|
||||||
@@ -909,6 +967,11 @@ class MaskFormerImageProcessor(BaseImageProcessor):
|
|||||||
If set, will return tensors instead of NumPy arrays. If set to `'pt'`, return PyTorch `torch.Tensor`
|
If set, will return tensors instead of NumPy arrays. If set to `'pt'`, return PyTorch `torch.Tensor`
|
||||||
objects.
|
objects.
|
||||||
|
|
||||||
|
pad_size (`Dict[str, int]`, *optional*):
|
||||||
|
The size `{"height": int, "width" int}` to pad the images to. Must be larger than any image size
|
||||||
|
provided for preprocessing. If `pad_size` is not provided, images will be padded to the largest
|
||||||
|
height and width in the batch.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
[`BatchFeature`]: A [`BatchFeature`] with the following fields:
|
[`BatchFeature`]: A [`BatchFeature`] with the following fields:
|
||||||
|
|
||||||
@@ -930,7 +993,7 @@ class MaskFormerImageProcessor(BaseImageProcessor):
|
|||||||
input_data_format = infer_channel_dimension_format(pixel_values_list[0])
|
input_data_format = infer_channel_dimension_format(pixel_values_list[0])
|
||||||
|
|
||||||
encoded_inputs = self.pad(
|
encoded_inputs = self.pad(
|
||||||
pixel_values_list, return_tensors=return_tensors, input_data_format=input_data_format
|
pixel_values_list, return_tensors=return_tensors, input_data_format=input_data_format, pad_size=pad_size
|
||||||
)
|
)
|
||||||
|
|
||||||
if segmentation_maps is not None:
|
if segmentation_maps is not None:
|
||||||
|
|||||||
@@ -0,0 +1,775 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
# Copyright 2025 The HuggingFace Inc. team. All rights reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
"""Fast Image processor class for MaskFormer."""
|
||||||
|
|
||||||
|
import math
|
||||||
|
import warnings
|
||||||
|
from typing import TYPE_CHECKING, Any, Optional, Union
|
||||||
|
|
||||||
|
from ...image_processing_utils import BatchFeature, get_size_dict
|
||||||
|
from ...image_processing_utils_fast import (
|
||||||
|
BaseImageProcessorFast,
|
||||||
|
DefaultFastImageProcessorKwargs,
|
||||||
|
SizeDict,
|
||||||
|
get_image_size_for_max_height_width,
|
||||||
|
get_max_height_width,
|
||||||
|
group_images_by_shape,
|
||||||
|
reorder_images,
|
||||||
|
)
|
||||||
|
from ...image_utils import (
|
||||||
|
IMAGENET_DEFAULT_MEAN,
|
||||||
|
IMAGENET_DEFAULT_STD,
|
||||||
|
ChannelDimension,
|
||||||
|
ImageInput,
|
||||||
|
PILImageResampling,
|
||||||
|
)
|
||||||
|
from ...processing_utils import Unpack
|
||||||
|
from ...utils import (
|
||||||
|
TensorType,
|
||||||
|
auto_docstring,
|
||||||
|
is_torch_available,
|
||||||
|
is_torchvision_available,
|
||||||
|
is_torchvision_v2_available,
|
||||||
|
logging,
|
||||||
|
)
|
||||||
|
from ...utils.deprecation import deprecate_kwarg
|
||||||
|
from .image_processing_maskformer import (
|
||||||
|
compute_segments,
|
||||||
|
convert_segmentation_to_rle,
|
||||||
|
get_size_with_aspect_ratio,
|
||||||
|
remove_low_and_no_objects,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from transformers import MaskFormerForInstanceSegmentationOutput
|
||||||
|
|
||||||
|
|
||||||
|
if is_torch_available():
|
||||||
|
import torch
|
||||||
|
from torch import nn
|
||||||
|
|
||||||
|
|
||||||
|
if is_torchvision_v2_available():
|
||||||
|
from torchvision.transforms.v2 import functional as F
|
||||||
|
elif is_torchvision_available():
|
||||||
|
from torchvision.transforms import functional as F
|
||||||
|
|
||||||
|
|
||||||
|
def convert_segmentation_map_to_binary_masks_fast(
|
||||||
|
segmentation_map: "torch.Tensor",
|
||||||
|
instance_id_to_semantic_id: Optional[dict[int, int]] = None,
|
||||||
|
ignore_index: Optional[int] = None,
|
||||||
|
do_reduce_labels: bool = False,
|
||||||
|
):
|
||||||
|
if do_reduce_labels and ignore_index is None:
|
||||||
|
raise ValueError("If `do_reduce_labels` is True, `ignore_index` must be provided.")
|
||||||
|
|
||||||
|
if do_reduce_labels:
|
||||||
|
segmentation_map = torch.where(segmentation_map == 0, ignore_index, segmentation_map - 1)
|
||||||
|
|
||||||
|
all_labels = torch.unique(segmentation_map)
|
||||||
|
|
||||||
|
if ignore_index is not None:
|
||||||
|
all_labels = all_labels[all_labels != ignore_index] # drop background label if applicable
|
||||||
|
|
||||||
|
binary_masks = [(segmentation_map == i) for i in all_labels]
|
||||||
|
if binary_masks:
|
||||||
|
binary_masks = torch.stack(binary_masks, dim=0)
|
||||||
|
else:
|
||||||
|
binary_masks = torch.zeros((0, *segmentation_map.shape), device=segmentation_map.device)
|
||||||
|
|
||||||
|
# Convert instance ids to class ids
|
||||||
|
if instance_id_to_semantic_id is not None:
|
||||||
|
labels = torch.zeros(all_labels.shape[0], device=segmentation_map.device)
|
||||||
|
|
||||||
|
for i, label in enumerate(all_labels):
|
||||||
|
class_id = instance_id_to_semantic_id[(label.item() + 1 if do_reduce_labels else label.item())]
|
||||||
|
labels[i] = class_id - 1 if do_reduce_labels else class_id
|
||||||
|
else:
|
||||||
|
labels = all_labels
|
||||||
|
return binary_masks.float(), labels.long()
|
||||||
|
|
||||||
|
|
||||||
|
class MaskFormerFastImageProcessorKwargs(DefaultFastImageProcessorKwargs):
|
||||||
|
r"""
|
||||||
|
size_divisor (`int`, *optional*, defaults to 32):
|
||||||
|
Some backbones need images divisible by a certain number. If not passed, it defaults to the value used in
|
||||||
|
Swin Transformer.
|
||||||
|
ignore_index (`int`, *optional*):
|
||||||
|
Label to be assigned to background pixels in segmentation maps. If provided, segmentation map pixels
|
||||||
|
denoted with 0 (background) will be replaced with `ignore_index`.
|
||||||
|
do_reduce_labels (`bool`, *optional*, defaults to `False`):
|
||||||
|
Whether or not to decrement all label values of segmentation maps by 1. Usually used for datasets where 0
|
||||||
|
is used for background, and background itself is not included in all classes of a dataset (e.g. ADE20k).
|
||||||
|
The background label will be replaced by `ignore_index`.
|
||||||
|
num_labels (`int`, *optional*):
|
||||||
|
The number of labels in the segmentation map.
|
||||||
|
do_pad (`bool`, *optional*, defaults to `True`):
|
||||||
|
Controls whether to pad the image. Can be overridden by the `do_pad` parameter in the `preprocess`
|
||||||
|
method. If `True`, padding will be applied to the bottom and right of the image with zeros.
|
||||||
|
If `pad_size` is provided, the image will be padded to the specified dimensions.
|
||||||
|
Otherwise, the image will be padded to the maximum height and width of the batch.
|
||||||
|
pad_size (`Dict[str, int]`, *optional*):
|
||||||
|
The size `{"height": int, "width" int}` to pad the images to. Must be larger than any image size
|
||||||
|
provided for preprocessing. If `pad_size` is not provided, images will be padded to the largest
|
||||||
|
height and width in the batch.
|
||||||
|
"""
|
||||||
|
|
||||||
|
size_divisor: Optional[int]
|
||||||
|
ignore_index: Optional[int]
|
||||||
|
do_reduce_labels: Optional[bool]
|
||||||
|
num_labels: Optional[int]
|
||||||
|
do_pad: Optional[bool]
|
||||||
|
pad_size: Optional[dict[str, int]]
|
||||||
|
|
||||||
|
|
||||||
|
@auto_docstring
|
||||||
|
class MaskFormerImageProcessorFast(BaseImageProcessorFast):
|
||||||
|
resample = PILImageResampling.BILINEAR
|
||||||
|
image_mean = IMAGENET_DEFAULT_MEAN
|
||||||
|
image_std = IMAGENET_DEFAULT_STD
|
||||||
|
size = {"shortest_edge": 800, "longest_edge": 1333}
|
||||||
|
default_to_square = False
|
||||||
|
do_resize = True
|
||||||
|
do_rescale = True
|
||||||
|
rescale_factor = 1 / 255
|
||||||
|
do_normalize = True
|
||||||
|
do_pad = True
|
||||||
|
model_input_names = ["pixel_values", "pixel_mask"]
|
||||||
|
size_divisor = 32
|
||||||
|
do_reduce_labels = False
|
||||||
|
valid_kwargs = MaskFormerFastImageProcessorKwargs
|
||||||
|
|
||||||
|
@deprecate_kwarg("reduce_labels", new_name="do_reduce_labels", version="4.44.0")
|
||||||
|
@deprecate_kwarg("size_divisibility", new_name="size_divisor", version="4.41.0")
|
||||||
|
@deprecate_kwarg("max_size", version="4.27.0", warn_if_greater_or_equal_version=True)
|
||||||
|
def __init__(self, **kwargs: Unpack[MaskFormerFastImageProcessorKwargs]) -> None:
|
||||||
|
if "pad_and_return_pixel_mask" in kwargs:
|
||||||
|
kwargs["do_pad"] = kwargs.pop("pad_and_return_pixel_mask")
|
||||||
|
|
||||||
|
size = kwargs.pop("size", None)
|
||||||
|
max_size = kwargs.pop("max_size", None)
|
||||||
|
|
||||||
|
if size is None and max_size is not None:
|
||||||
|
size = self.size
|
||||||
|
size["longest_edge"] = max_size
|
||||||
|
elif size is None:
|
||||||
|
size = self.size
|
||||||
|
|
||||||
|
self.size = get_size_dict(size, max_size=max_size, default_to_square=False)
|
||||||
|
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_dict(cls, image_processor_dict: dict[str, Any], **kwargs):
|
||||||
|
"""
|
||||||
|
Overrides the `from_dict` method from the base class to make sure parameters are updated if image processor is
|
||||||
|
created using from_dict and kwargs e.g. `MaskFormerImageProcessor.from_pretrained(checkpoint, max_size=800)`
|
||||||
|
"""
|
||||||
|
image_processor_dict = image_processor_dict.copy()
|
||||||
|
if "max_size" in kwargs:
|
||||||
|
image_processor_dict["max_size"] = kwargs.pop("max_size")
|
||||||
|
if "size_divisibility" in kwargs:
|
||||||
|
image_processor_dict["size_divisor"] = kwargs.pop("size_divisibility")
|
||||||
|
if "reduce_labels" in image_processor_dict:
|
||||||
|
image_processor_dict["do_reduce_labels"] = image_processor_dict.pop("reduce_labels")
|
||||||
|
return super().from_dict(image_processor_dict, **kwargs)
|
||||||
|
|
||||||
|
def to_dict(self) -> dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Serializes this instance to a Python dictionary. This method calls the superclass method and then removes the
|
||||||
|
`_max_size` attribute from the dictionary.
|
||||||
|
"""
|
||||||
|
image_processor_dict = super().to_dict()
|
||||||
|
image_processor_dict.pop("_max_size", None)
|
||||||
|
return image_processor_dict
|
||||||
|
|
||||||
|
def reduce_label(self, labels: list["torch.Tensor"]):
|
||||||
|
for idx in range(len(labels)):
|
||||||
|
label = labels[idx]
|
||||||
|
label = torch.where(label == 0, torch.tensor(255, dtype=label.dtype), label)
|
||||||
|
label = label - 1
|
||||||
|
label = torch.where(label == 254, torch.tensor(255, dtype=label.dtype), label)
|
||||||
|
labels[idx] = label
|
||||||
|
|
||||||
|
def resize(
|
||||||
|
self,
|
||||||
|
image: torch.Tensor,
|
||||||
|
size: SizeDict,
|
||||||
|
size_divisor: int = 0,
|
||||||
|
interpolation: "F.InterpolationMode" = None,
|
||||||
|
**kwargs,
|
||||||
|
) -> torch.Tensor:
|
||||||
|
"""
|
||||||
|
Resize the image to the given size. Size can be `min_size` (scalar) or `(height, width)` tuple. If size is an
|
||||||
|
int, smaller edge of the image will be matched to this number.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
image (`torch.Tensor`):
|
||||||
|
Image to resize.
|
||||||
|
size (`SizeDict`):
|
||||||
|
Size of the image's `(height, width)` dimensions after resizing. Available options are:
|
||||||
|
- `{"height": int, "width": int}`: The image will be resized to the exact size `(height, width)`.
|
||||||
|
Do NOT keep the aspect ratio.
|
||||||
|
- `{"shortest_edge": int, "longest_edge": int}`: The image will be resized to a maximum size respecting
|
||||||
|
the aspect ratio and keeping the shortest edge less or equal to `shortest_edge` and the longest edge
|
||||||
|
less or equal to `longest_edge`.
|
||||||
|
- `{"max_height": int, "max_width": int}`: The image will be resized to the maximum size respecting the
|
||||||
|
aspect ratio and keeping the height less or equal to `max_height` and the width less or equal to
|
||||||
|
`max_width`.
|
||||||
|
size_divisor (`int`, *optional*, defaults to 0):
|
||||||
|
If `size_divisor` is given, the output image size will be divisible by the number.
|
||||||
|
interpolation (`InterpolationMode`, *optional*, defaults to `InterpolationMode.BILINEAR`):
|
||||||
|
Resampling filter to use if resizing the image.
|
||||||
|
"""
|
||||||
|
interpolation = interpolation if interpolation is not None else F.InterpolationMode.BILINEAR
|
||||||
|
if size.shortest_edge and size.longest_edge:
|
||||||
|
# Resize the image so that the shortest edge or the longest edge is of the given size
|
||||||
|
# while maintaining the aspect ratio of the original image.
|
||||||
|
new_size = get_size_with_aspect_ratio(
|
||||||
|
image.size()[-2:],
|
||||||
|
size["shortest_edge"],
|
||||||
|
size["longest_edge"],
|
||||||
|
)
|
||||||
|
elif size.max_height and size.max_width:
|
||||||
|
new_size = get_image_size_for_max_height_width(image.size()[-2:], size["max_height"], size["max_width"])
|
||||||
|
elif size.height and size.width:
|
||||||
|
new_size = (size["height"], size["width"])
|
||||||
|
else:
|
||||||
|
raise ValueError(
|
||||||
|
"Size must contain 'height' and 'width' keys or 'shortest_edge' and 'longest_edge' keys. Got"
|
||||||
|
f" {size.keys()}."
|
||||||
|
)
|
||||||
|
if size_divisor > 0:
|
||||||
|
height, width = new_size
|
||||||
|
height = int(math.ceil(height / size_divisor) * size_divisor)
|
||||||
|
width = int(math.ceil(width / size_divisor) * size_divisor)
|
||||||
|
new_size = (height, width)
|
||||||
|
|
||||||
|
image = F.resize(
|
||||||
|
image,
|
||||||
|
size=new_size,
|
||||||
|
interpolation=interpolation,
|
||||||
|
**kwargs,
|
||||||
|
)
|
||||||
|
return image
|
||||||
|
|
||||||
|
def pad(
|
||||||
|
self,
|
||||||
|
images: torch.Tensor,
|
||||||
|
padded_size: tuple[int, int],
|
||||||
|
segmentation_maps: Optional[torch.Tensor] = None,
|
||||||
|
fill: int = 0,
|
||||||
|
ignore_index: int = 255,
|
||||||
|
) -> BatchFeature:
|
||||||
|
original_size = images.size()[-2:]
|
||||||
|
padding_bottom = padded_size[0] - original_size[0]
|
||||||
|
padding_right = padded_size[1] - original_size[1]
|
||||||
|
if padding_bottom < 0 or padding_right < 0:
|
||||||
|
raise ValueError(
|
||||||
|
f"Padding dimensions are negative. Please make sure that the padded size is larger than the "
|
||||||
|
f"original size. Got padded size: {padded_size}, original size: {original_size}."
|
||||||
|
)
|
||||||
|
if original_size != padded_size:
|
||||||
|
padding = [0, 0, padding_right, padding_bottom]
|
||||||
|
images = F.pad(images, padding, fill=fill)
|
||||||
|
if segmentation_maps is not None:
|
||||||
|
segmentation_maps = F.pad(segmentation_maps, padding, fill=ignore_index)
|
||||||
|
|
||||||
|
# Make a pixel mask for the image, where 1 indicates a valid pixel and 0 indicates padding.
|
||||||
|
pixel_mask = torch.zeros((images.shape[0], *padded_size), dtype=torch.int64, device=images.device)
|
||||||
|
pixel_mask[:, : original_size[0], : original_size[1]] = 1
|
||||||
|
|
||||||
|
return images, pixel_mask, segmentation_maps
|
||||||
|
|
||||||
|
@auto_docstring
|
||||||
|
def preprocess(
|
||||||
|
self,
|
||||||
|
images: ImageInput,
|
||||||
|
segmentation_maps: Optional[ImageInput] = None,
|
||||||
|
instance_id_to_semantic_id: Optional[Union[list[dict[int, int]], dict[int, int]]] = None,
|
||||||
|
**kwargs: Unpack[MaskFormerFastImageProcessorKwargs],
|
||||||
|
) -> BatchFeature:
|
||||||
|
r"""
|
||||||
|
segmentation_maps (`ImageInput`, *optional*):
|
||||||
|
The segmentation maps.
|
||||||
|
instance_id_to_semantic_id (`Union[list[dict[int, int]], dict[int, int]]`, *optional*):
|
||||||
|
A mapping from instance IDs to semantic IDs.
|
||||||
|
"""
|
||||||
|
return super().preprocess(
|
||||||
|
images,
|
||||||
|
segmentation_maps,
|
||||||
|
instance_id_to_semantic_id,
|
||||||
|
**kwargs,
|
||||||
|
)
|
||||||
|
|
||||||
|
def _preprocess_image_like_inputs(
|
||||||
|
self,
|
||||||
|
images: ImageInput,
|
||||||
|
segmentation_maps: ImageInput,
|
||||||
|
instance_id_to_semantic_id: Optional[Union[list[dict[int, int]], dict[int, int]]],
|
||||||
|
do_convert_rgb: bool,
|
||||||
|
input_data_format: ChannelDimension,
|
||||||
|
device: Optional[Union[str, "torch.device"]] = None,
|
||||||
|
**kwargs: Unpack[MaskFormerFastImageProcessorKwargs],
|
||||||
|
) -> BatchFeature:
|
||||||
|
"""
|
||||||
|
Preprocess image-like inputs.
|
||||||
|
To be overriden by subclasses when image-like inputs other than images should be processed.
|
||||||
|
It can be used for segmentation maps, depth maps, etc.
|
||||||
|
"""
|
||||||
|
# Prepare input images
|
||||||
|
images = self._prepare_image_like_inputs(
|
||||||
|
images=images, do_convert_rgb=do_convert_rgb, input_data_format=input_data_format, device=device
|
||||||
|
)
|
||||||
|
if segmentation_maps is not None:
|
||||||
|
segmentation_maps = self._prepare_image_like_inputs(
|
||||||
|
images=segmentation_maps,
|
||||||
|
expected_ndims=2,
|
||||||
|
do_convert_rgb=False,
|
||||||
|
input_data_format=ChannelDimension.FIRST,
|
||||||
|
)
|
||||||
|
return self._preprocess(images, segmentation_maps, instance_id_to_semantic_id, **kwargs)
|
||||||
|
|
||||||
|
def _preprocess(
|
||||||
|
self,
|
||||||
|
images: list["torch.Tensor"],
|
||||||
|
segmentation_maps: Optional["torch.Tensor"],
|
||||||
|
instance_id_to_semantic_id: Optional[dict[int, int]],
|
||||||
|
do_resize: Optional[bool],
|
||||||
|
size: Optional[dict[str, int]],
|
||||||
|
pad_size: Optional[dict[str, int]],
|
||||||
|
size_divisor: Optional[int],
|
||||||
|
interpolation: Optional[Union["PILImageResampling", "F.InterpolationMode"]],
|
||||||
|
do_rescale: Optional[bool],
|
||||||
|
rescale_factor: Optional[float],
|
||||||
|
do_normalize: Optional[bool],
|
||||||
|
image_mean: Optional[Union[float, list[float]]],
|
||||||
|
image_std: Optional[Union[float, list[float]]],
|
||||||
|
ignore_index: Optional[int],
|
||||||
|
do_reduce_labels: Optional[bool],
|
||||||
|
disable_grouping: Optional[bool],
|
||||||
|
return_tensors: Optional[Union[str, TensorType]],
|
||||||
|
**kwargs,
|
||||||
|
) -> BatchFeature:
|
||||||
|
if segmentation_maps is not None and len(images) != len(segmentation_maps):
|
||||||
|
raise ValueError("Images and segmentation maps must have the same length.")
|
||||||
|
|
||||||
|
# Group images by size for batched resizing
|
||||||
|
grouped_images, grouped_images_index = group_images_by_shape(images, disable_grouping=disable_grouping)
|
||||||
|
resized_images_grouped = {}
|
||||||
|
if segmentation_maps is not None:
|
||||||
|
grouped_segmentation_maps, grouped_segmentation_maps_index = group_images_by_shape(
|
||||||
|
segmentation_maps, disable_grouping=disable_grouping
|
||||||
|
)
|
||||||
|
resized_segmentation_maps_grouped = {}
|
||||||
|
for shape, stacked_images in grouped_images.items():
|
||||||
|
if do_resize:
|
||||||
|
stacked_images = self.resize(
|
||||||
|
image=stacked_images, size=size, size_divisor=size_divisor, interpolation=interpolation
|
||||||
|
)
|
||||||
|
if segmentation_maps is not None:
|
||||||
|
stacked_segmentation_maps = self.resize(
|
||||||
|
image=grouped_segmentation_maps[shape],
|
||||||
|
size=size,
|
||||||
|
size_divisor=size_divisor,
|
||||||
|
interpolation=F.InterpolationMode.NEAREST_EXACT,
|
||||||
|
)
|
||||||
|
resized_images_grouped[shape] = stacked_images
|
||||||
|
if segmentation_maps is not None:
|
||||||
|
resized_segmentation_maps_grouped[shape] = stacked_segmentation_maps
|
||||||
|
resized_images = reorder_images(resized_images_grouped, grouped_images_index)
|
||||||
|
if segmentation_maps is not None:
|
||||||
|
resized_segmentation_maps = reorder_images(
|
||||||
|
resized_segmentation_maps_grouped, grouped_segmentation_maps_index
|
||||||
|
)
|
||||||
|
if pad_size is not None:
|
||||||
|
padded_size = (pad_size["height"], pad_size["width"])
|
||||||
|
else:
|
||||||
|
padded_size = get_max_height_width(resized_images)
|
||||||
|
|
||||||
|
if segmentation_maps is not None:
|
||||||
|
mask_labels = []
|
||||||
|
class_labels = []
|
||||||
|
# Convert to list of binary masks and labels
|
||||||
|
for idx, segmentation_map in enumerate(resized_segmentation_maps):
|
||||||
|
if isinstance(instance_id_to_semantic_id, list):
|
||||||
|
instance_id = instance_id_to_semantic_id[idx]
|
||||||
|
else:
|
||||||
|
instance_id = instance_id_to_semantic_id
|
||||||
|
# Use instance2class_id mapping per image
|
||||||
|
masks, classes = convert_segmentation_map_to_binary_masks_fast(
|
||||||
|
segmentation_map.squeeze(0),
|
||||||
|
instance_id,
|
||||||
|
ignore_index=ignore_index,
|
||||||
|
do_reduce_labels=do_reduce_labels,
|
||||||
|
)
|
||||||
|
mask_labels.append(masks)
|
||||||
|
class_labels.append(classes)
|
||||||
|
|
||||||
|
grouped_images, grouped_images_index = group_images_by_shape(resized_images, disable_grouping=disable_grouping)
|
||||||
|
processed_images_grouped = {}
|
||||||
|
processed_pixel_masks_grouped = {}
|
||||||
|
if segmentation_maps is not None:
|
||||||
|
grouped_segmentation_maps, grouped_segmentation_maps_index = group_images_by_shape(
|
||||||
|
mask_labels, disable_grouping=disable_grouping
|
||||||
|
)
|
||||||
|
processed_segmentation_maps_grouped = {}
|
||||||
|
for shape, stacked_images in grouped_images.items():
|
||||||
|
# Fused rescale and normalize
|
||||||
|
stacked_images = self.rescale_and_normalize(
|
||||||
|
stacked_images, do_rescale, rescale_factor, do_normalize, image_mean, image_std
|
||||||
|
)
|
||||||
|
padded_images, pixel_masks, padded_segmentation_maps = self.pad(
|
||||||
|
images=stacked_images,
|
||||||
|
segmentation_maps=grouped_segmentation_maps[shape] if segmentation_maps is not None else None,
|
||||||
|
padded_size=padded_size,
|
||||||
|
ignore_index=ignore_index,
|
||||||
|
)
|
||||||
|
processed_images_grouped[shape] = padded_images
|
||||||
|
processed_pixel_masks_grouped[shape] = pixel_masks
|
||||||
|
if segmentation_maps is not None:
|
||||||
|
processed_segmentation_maps_grouped[shape] = padded_segmentation_maps.squeeze(1)
|
||||||
|
processed_images = reorder_images(processed_images_grouped, grouped_images_index)
|
||||||
|
processed_pixel_masks = reorder_images(processed_pixel_masks_grouped, grouped_images_index)
|
||||||
|
encoded_inputs = BatchFeature(
|
||||||
|
data={
|
||||||
|
"pixel_values": torch.stack(processed_images, dim=0) if return_tensors else processed_images,
|
||||||
|
"pixel_mask": torch.stack(processed_pixel_masks, dim=0) if return_tensors else processed_pixel_masks,
|
||||||
|
},
|
||||||
|
tensor_type=return_tensors,
|
||||||
|
)
|
||||||
|
if segmentation_maps is not None:
|
||||||
|
mask_labels = reorder_images(processed_segmentation_maps_grouped, grouped_segmentation_maps_index)
|
||||||
|
# we cannot batch them since they don't share a common class size
|
||||||
|
encoded_inputs["mask_labels"] = mask_labels
|
||||||
|
encoded_inputs["class_labels"] = class_labels
|
||||||
|
|
||||||
|
return encoded_inputs
|
||||||
|
|
||||||
|
# Copied from transformers.models.maskformer.image_processing_maskformer.MaskFormerImageProcessor.post_process_segmentation
|
||||||
|
def post_process_segmentation(
|
||||||
|
self, outputs: "MaskFormerForInstanceSegmentationOutput", target_size: Optional[tuple[int, int]] = None
|
||||||
|
) -> "torch.Tensor":
|
||||||
|
"""
|
||||||
|
Converts the output of [`MaskFormerForInstanceSegmentationOutput`] into image segmentation predictions. Only
|
||||||
|
supports PyTorch.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
outputs ([`MaskFormerForInstanceSegmentationOutput`]):
|
||||||
|
The outputs from [`MaskFormerForInstanceSegmentation`].
|
||||||
|
|
||||||
|
target_size (`tuple[int, int]`, *optional*):
|
||||||
|
If set, the `masks_queries_logits` will be resized to `target_size`.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
`torch.Tensor`:
|
||||||
|
A tensor of shape (`batch_size, num_class_labels, height, width`).
|
||||||
|
"""
|
||||||
|
warnings.warn(
|
||||||
|
"`post_process_segmentation` is deprecated and will be removed in v5 of Transformers, please use"
|
||||||
|
" `post_process_instance_segmentation`",
|
||||||
|
FutureWarning,
|
||||||
|
)
|
||||||
|
|
||||||
|
# class_queries_logits has shape [BATCH, QUERIES, CLASSES + 1]
|
||||||
|
class_queries_logits = outputs.class_queries_logits
|
||||||
|
# masks_queries_logits has shape [BATCH, QUERIES, HEIGHT, WIDTH]
|
||||||
|
masks_queries_logits = outputs.masks_queries_logits
|
||||||
|
if target_size is not None:
|
||||||
|
masks_queries_logits = torch.nn.functional.interpolate(
|
||||||
|
masks_queries_logits,
|
||||||
|
size=target_size,
|
||||||
|
mode="bilinear",
|
||||||
|
align_corners=False,
|
||||||
|
)
|
||||||
|
# remove the null class `[..., :-1]`
|
||||||
|
masks_classes = class_queries_logits.softmax(dim=-1)[..., :-1]
|
||||||
|
# mask probs has shape [BATCH, QUERIES, HEIGHT, WIDTH]
|
||||||
|
masks_probs = masks_queries_logits.sigmoid()
|
||||||
|
# now we want to sum over the queries,
|
||||||
|
# $ out_{c,h,w} = \sum_q p_{q,c} * m_{q,h,w} $
|
||||||
|
# where $ softmax(p) \in R^{q, c} $ is the mask classes
|
||||||
|
# and $ sigmoid(m) \in R^{q, h, w}$ is the mask probabilities
|
||||||
|
# b(atch)q(uery)c(lasses), b(atch)q(uery)h(eight)w(idth)
|
||||||
|
segmentation = torch.einsum("bqc, bqhw -> bchw", masks_classes, masks_probs)
|
||||||
|
|
||||||
|
return segmentation
|
||||||
|
|
||||||
|
# Copied from transformers.models.maskformer.image_processing_maskformer.MaskFormerImageProcessor.post_process_semantic_segmentation
|
||||||
|
def post_process_semantic_segmentation(
|
||||||
|
self, outputs, target_sizes: Optional[list[tuple[int, int]]] = None
|
||||||
|
) -> "torch.Tensor":
|
||||||
|
"""
|
||||||
|
Converts the output of [`MaskFormerForInstanceSegmentation`] into semantic segmentation maps. Only supports
|
||||||
|
PyTorch.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
outputs ([`MaskFormerForInstanceSegmentation`]):
|
||||||
|
Raw outputs of the model.
|
||||||
|
target_sizes (`list[tuple[int, int]]`, *optional*):
|
||||||
|
List of length (batch_size), where each list item (`tuple[int, int]]`) corresponds to the requested
|
||||||
|
final size (height, width) of each prediction. If left to None, predictions will not be resized.
|
||||||
|
Returns:
|
||||||
|
`list[torch.Tensor]`:
|
||||||
|
A list of length `batch_size`, where each item is a semantic segmentation map of shape (height, width)
|
||||||
|
corresponding to the target_sizes entry (if `target_sizes` is specified). Each entry of each
|
||||||
|
`torch.Tensor` correspond to a semantic class id.
|
||||||
|
"""
|
||||||
|
class_queries_logits = outputs.class_queries_logits # [batch_size, num_queries, num_classes+1]
|
||||||
|
masks_queries_logits = outputs.masks_queries_logits # [batch_size, num_queries, height, width]
|
||||||
|
|
||||||
|
# Remove the null class `[..., :-1]`
|
||||||
|
masks_classes = class_queries_logits.softmax(dim=-1)[..., :-1]
|
||||||
|
masks_probs = masks_queries_logits.sigmoid() # [batch_size, num_queries, height, width]
|
||||||
|
|
||||||
|
# Semantic segmentation logits of shape (batch_size, num_classes, height, width)
|
||||||
|
segmentation = torch.einsum("bqc, bqhw -> bchw", masks_classes, masks_probs)
|
||||||
|
batch_size = class_queries_logits.shape[0]
|
||||||
|
|
||||||
|
# Resize logits and compute semantic segmentation maps
|
||||||
|
if target_sizes is not None:
|
||||||
|
if batch_size != len(target_sizes):
|
||||||
|
raise ValueError(
|
||||||
|
"Make sure that you pass in as many target sizes as the batch dimension of the logits"
|
||||||
|
)
|
||||||
|
|
||||||
|
semantic_segmentation = []
|
||||||
|
for idx in range(batch_size):
|
||||||
|
resized_logits = torch.nn.functional.interpolate(
|
||||||
|
segmentation[idx].unsqueeze(dim=0), size=target_sizes[idx], mode="bilinear", align_corners=False
|
||||||
|
)
|
||||||
|
semantic_map = resized_logits[0].argmax(dim=0)
|
||||||
|
semantic_segmentation.append(semantic_map)
|
||||||
|
else:
|
||||||
|
semantic_segmentation = segmentation.argmax(dim=1)
|
||||||
|
semantic_segmentation = [semantic_segmentation[i] for i in range(semantic_segmentation.shape[0])]
|
||||||
|
|
||||||
|
return semantic_segmentation
|
||||||
|
|
||||||
|
# Copied from transformers.models.maskformer.image_processing_maskformer.MaskFormerImageProcessor.post_process_instance_segmentation
|
||||||
|
def post_process_instance_segmentation(
|
||||||
|
self,
|
||||||
|
outputs,
|
||||||
|
threshold: float = 0.5,
|
||||||
|
mask_threshold: float = 0.5,
|
||||||
|
overlap_mask_area_threshold: float = 0.8,
|
||||||
|
target_sizes: Optional[list[tuple[int, int]]] = None,
|
||||||
|
return_coco_annotation: Optional[bool] = False,
|
||||||
|
return_binary_maps: Optional[bool] = False,
|
||||||
|
) -> list[dict]:
|
||||||
|
"""
|
||||||
|
Converts the output of [`MaskFormerForInstanceSegmentationOutput`] into instance segmentation predictions. Only
|
||||||
|
supports PyTorch. If instances could overlap, set either return_coco_annotation or return_binary_maps
|
||||||
|
to `True` to get the correct segmentation result.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
outputs ([`MaskFormerForInstanceSegmentation`]):
|
||||||
|
Raw outputs of the model.
|
||||||
|
threshold (`float`, *optional*, defaults to 0.5):
|
||||||
|
The probability score threshold to keep predicted instance masks.
|
||||||
|
mask_threshold (`float`, *optional*, defaults to 0.5):
|
||||||
|
Threshold to use when turning the predicted masks into binary values.
|
||||||
|
overlap_mask_area_threshold (`float`, *optional*, defaults to 0.8):
|
||||||
|
The overlap mask area threshold to merge or discard small disconnected parts within each binary
|
||||||
|
instance mask.
|
||||||
|
target_sizes (`list[Tuple]`, *optional*):
|
||||||
|
List of length (batch_size), where each list item (`tuple[int, int]]`) corresponds to the requested
|
||||||
|
final size (height, width) of each prediction. If left to None, predictions will not be resized.
|
||||||
|
return_coco_annotation (`bool`, *optional*, defaults to `False`):
|
||||||
|
If set to `True`, segmentation maps are returned in COCO run-length encoding (RLE) format.
|
||||||
|
return_binary_maps (`bool`, *optional*, defaults to `False`):
|
||||||
|
If set to `True`, segmentation maps are returned as a concatenated tensor of binary segmentation maps
|
||||||
|
(one per detected instance).
|
||||||
|
Returns:
|
||||||
|
`list[Dict]`: A list of dictionaries, one per image, each dictionary containing two keys:
|
||||||
|
- **segmentation** -- A tensor of shape `(height, width)` where each pixel represents a `segment_id`, or
|
||||||
|
`list[List]` run-length encoding (RLE) of the segmentation map if return_coco_annotation is set to
|
||||||
|
`True`, or a tensor of shape `(num_instances, height, width)` if return_binary_maps is set to `True`.
|
||||||
|
Set to `None` if no mask if found above `threshold`.
|
||||||
|
- **segments_info** -- A dictionary that contains additional information on each segment.
|
||||||
|
- **id** -- An integer representing the `segment_id`.
|
||||||
|
- **label_id** -- An integer representing the label / semantic class id corresponding to `segment_id`.
|
||||||
|
- **score** -- Prediction score of segment with `segment_id`.
|
||||||
|
"""
|
||||||
|
if return_coco_annotation and return_binary_maps:
|
||||||
|
raise ValueError("return_coco_annotation and return_binary_maps can not be both set to True.")
|
||||||
|
|
||||||
|
# [batch_size, num_queries, num_classes+1]
|
||||||
|
class_queries_logits = outputs.class_queries_logits
|
||||||
|
# [batch_size, num_queries, height, width]
|
||||||
|
masks_queries_logits = outputs.masks_queries_logits
|
||||||
|
|
||||||
|
device = masks_queries_logits.device
|
||||||
|
num_classes = class_queries_logits.shape[-1] - 1
|
||||||
|
num_queries = class_queries_logits.shape[-2]
|
||||||
|
|
||||||
|
# Loop over items in batch size
|
||||||
|
results: list[dict[str, TensorType]] = []
|
||||||
|
|
||||||
|
for i in range(class_queries_logits.shape[0]):
|
||||||
|
mask_pred = masks_queries_logits[i]
|
||||||
|
mask_cls = class_queries_logits[i]
|
||||||
|
|
||||||
|
scores = torch.nn.functional.softmax(mask_cls, dim=-1)[:, :-1]
|
||||||
|
labels = torch.arange(num_classes, device=device).unsqueeze(0).repeat(num_queries, 1).flatten(0, 1)
|
||||||
|
|
||||||
|
scores_per_image, topk_indices = scores.flatten(0, 1).topk(num_queries, sorted=False)
|
||||||
|
labels_per_image = labels[topk_indices]
|
||||||
|
|
||||||
|
topk_indices = torch.div(topk_indices, num_classes, rounding_mode="floor")
|
||||||
|
mask_pred = mask_pred[topk_indices]
|
||||||
|
pred_masks = (mask_pred > 0).float()
|
||||||
|
|
||||||
|
# Calculate average mask prob
|
||||||
|
mask_scores_per_image = (mask_pred.sigmoid().flatten(1) * pred_masks.flatten(1)).sum(1) / (
|
||||||
|
pred_masks.flatten(1).sum(1) + 1e-6
|
||||||
|
)
|
||||||
|
pred_scores = scores_per_image * mask_scores_per_image
|
||||||
|
pred_classes = labels_per_image
|
||||||
|
|
||||||
|
segmentation = torch.zeros(masks_queries_logits.shape[2:]) - 1
|
||||||
|
if target_sizes is not None:
|
||||||
|
segmentation = torch.zeros(target_sizes[i]) - 1
|
||||||
|
pred_masks = torch.nn.functional.interpolate(
|
||||||
|
pred_masks.unsqueeze(0), size=target_sizes[i], mode="nearest"
|
||||||
|
)[0]
|
||||||
|
|
||||||
|
instance_maps, segments = [], []
|
||||||
|
current_segment_id = 0
|
||||||
|
for j in range(num_queries):
|
||||||
|
score = pred_scores[j].item()
|
||||||
|
|
||||||
|
if not torch.all(pred_masks[j] == 0) and score >= threshold:
|
||||||
|
segmentation[pred_masks[j] == 1] = current_segment_id
|
||||||
|
segments.append(
|
||||||
|
{
|
||||||
|
"id": current_segment_id,
|
||||||
|
"label_id": pred_classes[j].item(),
|
||||||
|
"was_fused": False,
|
||||||
|
"score": round(score, 6),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
current_segment_id += 1
|
||||||
|
instance_maps.append(pred_masks[j])
|
||||||
|
|
||||||
|
# Return segmentation map in run-length encoding (RLE) format
|
||||||
|
if return_coco_annotation:
|
||||||
|
segmentation = convert_segmentation_to_rle(segmentation)
|
||||||
|
|
||||||
|
# Return a concatenated tensor of binary instance maps
|
||||||
|
if return_binary_maps and len(instance_maps) != 0:
|
||||||
|
segmentation = torch.stack(instance_maps, dim=0)
|
||||||
|
|
||||||
|
results.append({"segmentation": segmentation, "segments_info": segments})
|
||||||
|
return results
|
||||||
|
|
||||||
|
# Copied from transformers.models.maskformer.image_processing_maskformer.MaskFormerImageProcessor.post_process_panoptic_segmentation
|
||||||
|
def post_process_panoptic_segmentation(
|
||||||
|
self,
|
||||||
|
outputs,
|
||||||
|
threshold: float = 0.5,
|
||||||
|
mask_threshold: float = 0.5,
|
||||||
|
overlap_mask_area_threshold: float = 0.8,
|
||||||
|
label_ids_to_fuse: Optional[set[int]] = None,
|
||||||
|
target_sizes: Optional[list[tuple[int, int]]] = None,
|
||||||
|
) -> list[dict]:
|
||||||
|
"""
|
||||||
|
Converts the output of [`MaskFormerForInstanceSegmentationOutput`] into image panoptic segmentation
|
||||||
|
predictions. Only supports PyTorch.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
outputs ([`MaskFormerForInstanceSegmentationOutput`]):
|
||||||
|
The outputs from [`MaskFormerForInstanceSegmentation`].
|
||||||
|
threshold (`float`, *optional*, defaults to 0.5):
|
||||||
|
The probability score threshold to keep predicted instance masks.
|
||||||
|
mask_threshold (`float`, *optional*, defaults to 0.5):
|
||||||
|
Threshold to use when turning the predicted masks into binary values.
|
||||||
|
overlap_mask_area_threshold (`float`, *optional*, defaults to 0.8):
|
||||||
|
The overlap mask area threshold to merge or discard small disconnected parts within each binary
|
||||||
|
instance mask.
|
||||||
|
label_ids_to_fuse (`Set[int]`, *optional*):
|
||||||
|
The labels in this state will have all their instances be fused together. For instance we could say
|
||||||
|
there can only be one sky in an image, but several persons, so the label ID for sky would be in that
|
||||||
|
set, but not the one for person.
|
||||||
|
target_sizes (`list[Tuple]`, *optional*):
|
||||||
|
List of length (batch_size), where each list item (`tuple[int, int]]`) corresponds to the requested
|
||||||
|
final size (height, width) of each prediction in batch. If left to None, predictions will not be
|
||||||
|
resized.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
`list[Dict]`: A list of dictionaries, one per image, each dictionary containing two keys:
|
||||||
|
- **segmentation** -- a tensor of shape `(height, width)` where each pixel represents a `segment_id`, set
|
||||||
|
to `None` if no mask if found above `threshold`. If `target_sizes` is specified, segmentation is resized
|
||||||
|
to the corresponding `target_sizes` entry.
|
||||||
|
- **segments_info** -- A dictionary that contains additional information on each segment.
|
||||||
|
- **id** -- an integer representing the `segment_id`.
|
||||||
|
- **label_id** -- An integer representing the label / semantic class id corresponding to `segment_id`.
|
||||||
|
- **was_fused** -- a boolean, `True` if `label_id` was in `label_ids_to_fuse`, `False` otherwise.
|
||||||
|
Multiple instances of the same class / label were fused and assigned a single `segment_id`.
|
||||||
|
- **score** -- Prediction score of segment with `segment_id`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if label_ids_to_fuse is None:
|
||||||
|
logger.warning("`label_ids_to_fuse` unset. No instance will be fused.")
|
||||||
|
label_ids_to_fuse = set()
|
||||||
|
|
||||||
|
class_queries_logits = outputs.class_queries_logits # [batch_size, num_queries, num_classes+1]
|
||||||
|
masks_queries_logits = outputs.masks_queries_logits # [batch_size, num_queries, height, width]
|
||||||
|
|
||||||
|
batch_size = class_queries_logits.shape[0]
|
||||||
|
num_labels = class_queries_logits.shape[-1] - 1
|
||||||
|
|
||||||
|
mask_probs = masks_queries_logits.sigmoid() # [batch_size, num_queries, height, width]
|
||||||
|
|
||||||
|
# Predicted label and score of each query (batch_size, num_queries)
|
||||||
|
pred_scores, pred_labels = nn.functional.softmax(class_queries_logits, dim=-1).max(-1)
|
||||||
|
|
||||||
|
# Loop over items in batch size
|
||||||
|
results: list[dict[str, TensorType]] = []
|
||||||
|
|
||||||
|
for i in range(batch_size):
|
||||||
|
mask_probs_item, pred_scores_item, pred_labels_item = remove_low_and_no_objects(
|
||||||
|
mask_probs[i], pred_scores[i], pred_labels[i], threshold, num_labels
|
||||||
|
)
|
||||||
|
|
||||||
|
# No mask found
|
||||||
|
if mask_probs_item.shape[0] <= 0:
|
||||||
|
height, width = target_sizes[i] if target_sizes is not None else mask_probs_item.shape[1:]
|
||||||
|
segmentation = torch.zeros((height, width)) - 1
|
||||||
|
results.append({"segmentation": segmentation, "segments_info": []})
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Get segmentation map and segment information of batch item
|
||||||
|
target_size = target_sizes[i] if target_sizes is not None else None
|
||||||
|
segmentation, segments = compute_segments(
|
||||||
|
mask_probs=mask_probs_item,
|
||||||
|
pred_scores=pred_scores_item,
|
||||||
|
pred_labels=pred_labels_item,
|
||||||
|
mask_threshold=mask_threshold,
|
||||||
|
overlap_mask_area_threshold=overlap_mask_area_threshold,
|
||||||
|
label_ids_to_fuse=label_ids_to_fuse,
|
||||||
|
target_size=target_size,
|
||||||
|
)
|
||||||
|
|
||||||
|
results.append({"segmentation": segmentation, "segments_info": segments})
|
||||||
|
return results
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = ["MaskFormerImageProcessorFast"]
|
||||||
@@ -21,7 +21,7 @@ from huggingface_hub import hf_hub_download
|
|||||||
|
|
||||||
from transformers.image_utils import ChannelDimension
|
from transformers.image_utils import ChannelDimension
|
||||||
from transformers.testing_utils import require_torch, require_vision
|
from transformers.testing_utils import require_torch, require_vision
|
||||||
from transformers.utils import is_torch_available, is_vision_available
|
from transformers.utils import is_torch_available, is_torchvision_available, is_vision_available
|
||||||
|
|
||||||
from ...test_image_processing_common import ImageProcessingTestMixin, prepare_image_inputs
|
from ...test_image_processing_common import ImageProcessingTestMixin, prepare_image_inputs
|
||||||
|
|
||||||
@@ -34,6 +34,9 @@ if is_torch_available():
|
|||||||
from transformers.models.mask2former.image_processing_mask2former import binary_mask_to_rle
|
from transformers.models.mask2former.image_processing_mask2former import binary_mask_to_rle
|
||||||
from transformers.models.mask2former.modeling_mask2former import Mask2FormerForUniversalSegmentationOutput
|
from transformers.models.mask2former.modeling_mask2former import Mask2FormerForUniversalSegmentationOutput
|
||||||
|
|
||||||
|
if is_torchvision_available():
|
||||||
|
from transformers import Mask2FormerImageProcessorFast
|
||||||
|
|
||||||
if is_vision_available():
|
if is_vision_available():
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
@@ -54,6 +57,7 @@ class Mask2FormerImageProcessingTester:
|
|||||||
num_labels=10,
|
num_labels=10,
|
||||||
do_reduce_labels=True,
|
do_reduce_labels=True,
|
||||||
ignore_index=255,
|
ignore_index=255,
|
||||||
|
pad_size=None,
|
||||||
):
|
):
|
||||||
self.parent = parent
|
self.parent = parent
|
||||||
self.batch_size = batch_size
|
self.batch_size = batch_size
|
||||||
@@ -66,6 +70,7 @@ class Mask2FormerImageProcessingTester:
|
|||||||
self.image_mean = image_mean
|
self.image_mean = image_mean
|
||||||
self.image_std = image_std
|
self.image_std = image_std
|
||||||
self.size_divisor = 0
|
self.size_divisor = 0
|
||||||
|
self.pad_size = pad_size
|
||||||
# for the post_process_functions
|
# for the post_process_functions
|
||||||
self.batch_size = 2
|
self.batch_size = 2
|
||||||
self.num_queries = 3
|
self.num_queries = 3
|
||||||
@@ -87,6 +92,7 @@ class Mask2FormerImageProcessingTester:
|
|||||||
"num_labels": self.num_labels,
|
"num_labels": self.num_labels,
|
||||||
"do_reduce_labels": self.do_reduce_labels,
|
"do_reduce_labels": self.do_reduce_labels,
|
||||||
"ignore_index": self.ignore_index,
|
"ignore_index": self.ignore_index,
|
||||||
|
"pad_size": self.pad_size,
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_expected_values(self, image_inputs, batched=False):
|
def get_expected_values(self, image_inputs, batched=False):
|
||||||
@@ -145,10 +151,26 @@ class Mask2FormerImageProcessingTester:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# Copied from transformers.tests.models.beit.test_image_processing_beit.prepare_semantic_single_inputs
|
||||||
|
def prepare_semantic_single_inputs():
|
||||||
|
ds = load_dataset("hf-internal-testing/fixtures_ade20k", split="test")
|
||||||
|
example = ds[0]
|
||||||
|
return example["image"], example["map"]
|
||||||
|
|
||||||
|
|
||||||
|
# Copied from transformers.tests.models.beit.test_image_processing_beit.prepare_semantic_batch_inputs
|
||||||
|
def prepare_semantic_batch_inputs():
|
||||||
|
ds = load_dataset("hf-internal-testing/fixtures_ade20k", split="test")
|
||||||
|
return list(ds["image"][:2]), list(ds["map"][:2])
|
||||||
|
|
||||||
|
|
||||||
@require_torch
|
@require_torch
|
||||||
@require_vision
|
@require_vision
|
||||||
class Mask2FormerImageProcessingTest(ImageProcessingTestMixin, unittest.TestCase):
|
class Mask2FormerImageProcessingTest(ImageProcessingTestMixin, unittest.TestCase):
|
||||||
image_processing_class = Mask2FormerImageProcessor if (is_vision_available() and is_torch_available()) else None
|
image_processing_class = Mask2FormerImageProcessor if (is_vision_available() and is_torch_available()) else None
|
||||||
|
fast_image_processing_class = (
|
||||||
|
Mask2FormerImageProcessorFast if (is_vision_available() and is_torchvision_available()) else None
|
||||||
|
)
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super().setUp()
|
super().setUp()
|
||||||
@@ -159,25 +181,27 @@ class Mask2FormerImageProcessingTest(ImageProcessingTestMixin, unittest.TestCase
|
|||||||
return self.image_processor_tester.prepare_image_processor_dict()
|
return self.image_processor_tester.prepare_image_processor_dict()
|
||||||
|
|
||||||
def test_image_processor_properties(self):
|
def test_image_processor_properties(self):
|
||||||
image_processing = self.image_processing_class(**self.image_processor_dict)
|
for image_processing_class in self.image_processor_list:
|
||||||
self.assertTrue(hasattr(image_processing, "image_mean"))
|
image_processing = image_processing_class(**self.image_processor_dict)
|
||||||
self.assertTrue(hasattr(image_processing, "image_std"))
|
self.assertTrue(hasattr(image_processing, "image_mean"))
|
||||||
self.assertTrue(hasattr(image_processing, "do_normalize"))
|
self.assertTrue(hasattr(image_processing, "image_std"))
|
||||||
self.assertTrue(hasattr(image_processing, "do_resize"))
|
self.assertTrue(hasattr(image_processing, "do_normalize"))
|
||||||
self.assertTrue(hasattr(image_processing, "size"))
|
self.assertTrue(hasattr(image_processing, "do_resize"))
|
||||||
self.assertTrue(hasattr(image_processing, "ignore_index"))
|
self.assertTrue(hasattr(image_processing, "size"))
|
||||||
self.assertTrue(hasattr(image_processing, "num_labels"))
|
self.assertTrue(hasattr(image_processing, "ignore_index"))
|
||||||
|
self.assertTrue(hasattr(image_processing, "num_labels"))
|
||||||
|
|
||||||
def test_image_processor_from_dict_with_kwargs(self):
|
def test_image_processor_from_dict_with_kwargs(self):
|
||||||
image_processor = self.image_processing_class.from_dict(self.image_processor_dict)
|
for image_processing_class in self.image_processor_list:
|
||||||
self.assertEqual(image_processor.size, {"shortest_edge": 32, "longest_edge": 1333})
|
image_processor = image_processing_class.from_dict(self.image_processor_dict)
|
||||||
self.assertEqual(image_processor.size_divisor, 0)
|
self.assertEqual(image_processor.size, {"shortest_edge": 32, "longest_edge": 1333})
|
||||||
|
self.assertEqual(image_processor.size_divisor, 0)
|
||||||
|
|
||||||
image_processor = self.image_processing_class.from_dict(
|
image_processor = image_processing_class.from_dict(
|
||||||
self.image_processor_dict, size=42, max_size=84, size_divisibility=8
|
self.image_processor_dict, size=42, max_size=84, size_divisibility=8
|
||||||
)
|
)
|
||||||
self.assertEqual(image_processor.size, {"shortest_edge": 42, "longest_edge": 84})
|
self.assertEqual(image_processor.size, {"shortest_edge": 42, "longest_edge": 84})
|
||||||
self.assertEqual(image_processor.size_divisor, 8)
|
self.assertEqual(image_processor.size_divisor, 8)
|
||||||
|
|
||||||
def comm_get_image_processing_inputs(
|
def comm_get_image_processing_inputs(
|
||||||
self,
|
self,
|
||||||
@@ -225,15 +249,16 @@ class Mask2FormerImageProcessingTest(ImageProcessingTestMixin, unittest.TestCase
|
|||||||
def test_with_size_divisor(self):
|
def test_with_size_divisor(self):
|
||||||
size_divisors = [8, 16, 32]
|
size_divisors = [8, 16, 32]
|
||||||
weird_input_sizes = [(407, 802), (582, 1094)]
|
weird_input_sizes = [(407, 802), (582, 1094)]
|
||||||
for size_divisor in size_divisors:
|
for image_processing_class in self.image_processor_list:
|
||||||
image_processor_dict = {**self.image_processor_dict, **{"size_divisor": size_divisor}}
|
for size_divisor in size_divisors:
|
||||||
image_processing = self.image_processing_class(**image_processor_dict)
|
image_processor_dict = {**self.image_processor_dict, **{"size_divisor": size_divisor}}
|
||||||
for weird_input_size in weird_input_sizes:
|
image_processing = image_processing_class(**image_processor_dict)
|
||||||
inputs = image_processing([np.ones((3, *weird_input_size))], return_tensors="pt")
|
for weird_input_size in weird_input_sizes:
|
||||||
pixel_values = inputs["pixel_values"]
|
inputs = image_processing([np.ones((3, *weird_input_size))], return_tensors="pt")
|
||||||
# check if divisible
|
pixel_values = inputs["pixel_values"]
|
||||||
self.assertTrue((pixel_values.shape[-1] % size_divisor) == 0)
|
# check if divisible
|
||||||
self.assertTrue((pixel_values.shape[-2] % size_divisor) == 0)
|
self.assertTrue((pixel_values.shape[-1] % size_divisor) == 0)
|
||||||
|
self.assertTrue((pixel_values.shape[-2] % size_divisor) == 0)
|
||||||
|
|
||||||
def test_call_with_segmentation_maps(self):
|
def test_call_with_segmentation_maps(self):
|
||||||
def common(
|
def common(
|
||||||
@@ -463,81 +488,85 @@ class Mask2FormerImageProcessingTest(ImageProcessingTestMixin, unittest.TestCase
|
|||||||
self.assertEqual(rle[1], 45)
|
self.assertEqual(rle[1], 45)
|
||||||
|
|
||||||
def test_post_process_semantic_segmentation(self):
|
def test_post_process_semantic_segmentation(self):
|
||||||
fature_extractor = self.image_processing_class(num_labels=self.image_processor_tester.num_classes)
|
for image_processing_class in self.image_processor_list:
|
||||||
outputs = self.image_processor_tester.get_fake_mask2former_outputs()
|
fature_extractor = image_processing_class(num_labels=self.image_processor_tester.num_classes)
|
||||||
|
outputs = self.image_processor_tester.get_fake_mask2former_outputs()
|
||||||
|
|
||||||
segmentation = fature_extractor.post_process_semantic_segmentation(outputs)
|
segmentation = fature_extractor.post_process_semantic_segmentation(outputs)
|
||||||
|
|
||||||
self.assertEqual(len(segmentation), self.image_processor_tester.batch_size)
|
self.assertEqual(len(segmentation), self.image_processor_tester.batch_size)
|
||||||
self.assertEqual(segmentation[0].shape, (384, 384))
|
self.assertEqual(segmentation[0].shape, (384, 384))
|
||||||
|
|
||||||
target_sizes = [(1, 4) for i in range(self.image_processor_tester.batch_size)]
|
target_sizes = [(1, 4) for i in range(self.image_processor_tester.batch_size)]
|
||||||
segmentation = fature_extractor.post_process_semantic_segmentation(outputs, target_sizes=target_sizes)
|
segmentation = fature_extractor.post_process_semantic_segmentation(outputs, target_sizes=target_sizes)
|
||||||
|
|
||||||
self.assertEqual(segmentation[0].shape, target_sizes[0])
|
self.assertEqual(segmentation[0].shape, target_sizes[0])
|
||||||
|
|
||||||
def test_post_process_instance_segmentation(self):
|
def test_post_process_instance_segmentation(self):
|
||||||
image_processor = self.image_processing_class(num_labels=self.image_processor_tester.num_classes)
|
for image_processing_class in self.image_processor_list:
|
||||||
outputs = self.image_processor_tester.get_fake_mask2former_outputs()
|
image_processor = image_processing_class(num_labels=self.image_processor_tester.num_classes)
|
||||||
segmentation = image_processor.post_process_instance_segmentation(outputs, threshold=0)
|
outputs = self.image_processor_tester.get_fake_mask2former_outputs()
|
||||||
|
segmentation = image_processor.post_process_instance_segmentation(outputs, threshold=0)
|
||||||
|
|
||||||
self.assertTrue(len(segmentation) == self.image_processor_tester.batch_size)
|
self.assertTrue(len(segmentation) == self.image_processor_tester.batch_size)
|
||||||
for el in segmentation:
|
for el in segmentation:
|
||||||
self.assertTrue("segmentation" in el)
|
self.assertTrue("segmentation" in el)
|
||||||
self.assertTrue("segments_info" in el)
|
self.assertTrue("segments_info" in el)
|
||||||
self.assertEqual(type(el["segments_info"]), list)
|
self.assertEqual(type(el["segments_info"]), list)
|
||||||
self.assertEqual(el["segmentation"].shape, (384, 384))
|
self.assertEqual(el["segmentation"].shape, (384, 384))
|
||||||
|
|
||||||
segmentation = image_processor.post_process_instance_segmentation(
|
segmentation = image_processor.post_process_instance_segmentation(
|
||||||
outputs, threshold=0, return_binary_maps=True
|
outputs, threshold=0, return_binary_maps=True
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertTrue(len(segmentation) == self.image_processor_tester.batch_size)
|
self.assertTrue(len(segmentation) == self.image_processor_tester.batch_size)
|
||||||
for el in segmentation:
|
for el in segmentation:
|
||||||
self.assertTrue("segmentation" in el)
|
self.assertTrue("segmentation" in el)
|
||||||
self.assertTrue("segments_info" in el)
|
self.assertTrue("segments_info" in el)
|
||||||
self.assertEqual(type(el["segments_info"]), list)
|
self.assertEqual(type(el["segments_info"]), list)
|
||||||
self.assertEqual(len(el["segmentation"].shape), 3)
|
self.assertEqual(len(el["segmentation"].shape), 3)
|
||||||
self.assertEqual(el["segmentation"].shape[1:], (384, 384))
|
self.assertEqual(el["segmentation"].shape[1:], (384, 384))
|
||||||
|
|
||||||
def test_post_process_panoptic_segmentation(self):
|
def test_post_process_panoptic_segmentation(self):
|
||||||
image_processing = self.image_processing_class(num_labels=self.image_processor_tester.num_classes)
|
for image_processing_class in self.image_processor_list:
|
||||||
outputs = self.image_processor_tester.get_fake_mask2former_outputs()
|
image_processing = image_processing_class(num_labels=self.image_processor_tester.num_classes)
|
||||||
segmentation = image_processing.post_process_panoptic_segmentation(outputs, threshold=0)
|
outputs = self.image_processor_tester.get_fake_mask2former_outputs()
|
||||||
|
segmentation = image_processing.post_process_panoptic_segmentation(outputs, threshold=0)
|
||||||
|
|
||||||
self.assertTrue(len(segmentation) == self.image_processor_tester.batch_size)
|
self.assertTrue(len(segmentation) == self.image_processor_tester.batch_size)
|
||||||
for el in segmentation:
|
for el in segmentation:
|
||||||
self.assertTrue("segmentation" in el)
|
self.assertTrue("segmentation" in el)
|
||||||
self.assertTrue("segments_info" in el)
|
self.assertTrue("segments_info" in el)
|
||||||
self.assertEqual(type(el["segments_info"]), list)
|
self.assertEqual(type(el["segments_info"]), list)
|
||||||
self.assertEqual(el["segmentation"].shape, (384, 384))
|
self.assertEqual(el["segmentation"].shape, (384, 384))
|
||||||
|
|
||||||
def test_post_process_label_fusing(self):
|
def test_post_process_label_fusing(self):
|
||||||
image_processor = self.image_processing_class(num_labels=self.image_processor_tester.num_classes)
|
for image_processing_class in self.image_processor_list:
|
||||||
outputs = self.image_processor_tester.get_fake_mask2former_outputs()
|
image_processor = image_processing_class(num_labels=self.image_processor_tester.num_classes)
|
||||||
|
outputs = self.image_processor_tester.get_fake_mask2former_outputs()
|
||||||
|
|
||||||
segmentation = image_processor.post_process_panoptic_segmentation(
|
segmentation = image_processor.post_process_panoptic_segmentation(
|
||||||
outputs, threshold=0, mask_threshold=0, overlap_mask_area_threshold=0
|
outputs, threshold=0, mask_threshold=0, overlap_mask_area_threshold=0
|
||||||
)
|
)
|
||||||
unfused_segments = [el["segments_info"] for el in segmentation]
|
unfused_segments = [el["segments_info"] for el in segmentation]
|
||||||
|
|
||||||
fused_segmentation = image_processor.post_process_panoptic_segmentation(
|
fused_segmentation = image_processor.post_process_panoptic_segmentation(
|
||||||
outputs, threshold=0, mask_threshold=0, overlap_mask_area_threshold=0, label_ids_to_fuse={1}
|
outputs, threshold=0, mask_threshold=0, overlap_mask_area_threshold=0, label_ids_to_fuse={1}
|
||||||
)
|
)
|
||||||
fused_segments = [el["segments_info"] for el in fused_segmentation]
|
fused_segments = [el["segments_info"] for el in fused_segmentation]
|
||||||
|
|
||||||
for el_unfused, el_fused in zip(unfused_segments, fused_segments):
|
for el_unfused, el_fused in zip(unfused_segments, fused_segments):
|
||||||
if len(el_unfused) == 0:
|
if len(el_unfused) == 0:
|
||||||
self.assertEqual(len(el_unfused), len(el_fused))
|
self.assertEqual(len(el_unfused), len(el_fused))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Get number of segments to be fused
|
# Get number of segments to be fused
|
||||||
fuse_targets = [1 for el in el_unfused if el["label_id"] in {1}]
|
fuse_targets = [1 for el in el_unfused if el["label_id"] in {1}]
|
||||||
num_to_fuse = 0 if len(fuse_targets) == 0 else sum(fuse_targets) - 1
|
num_to_fuse = 0 if len(fuse_targets) == 0 else sum(fuse_targets) - 1
|
||||||
# Expected number of segments after fusing
|
# Expected number of segments after fusing
|
||||||
expected_num_segments = max([el["id"] for el in el_unfused]) - num_to_fuse
|
expected_num_segments = max([el["id"] for el in el_unfused]) - num_to_fuse
|
||||||
num_segments_fused = max([el["id"] for el in el_fused])
|
num_segments_fused = max([el["id"] for el in el_fused])
|
||||||
self.assertEqual(num_segments_fused, expected_num_segments)
|
self.assertEqual(num_segments_fused, expected_num_segments)
|
||||||
|
|
||||||
def test_removed_deprecated_kwargs(self):
|
def test_removed_deprecated_kwargs(self):
|
||||||
image_processor_dict = dict(self.image_processor_dict)
|
image_processor_dict = dict(self.image_processor_dict)
|
||||||
@@ -545,9 +574,58 @@ class Mask2FormerImageProcessingTest(ImageProcessingTestMixin, unittest.TestCase
|
|||||||
image_processor_dict["reduce_labels"] = True
|
image_processor_dict["reduce_labels"] = True
|
||||||
|
|
||||||
# test we are able to create the image processor with the deprecated kwargs
|
# test we are able to create the image processor with the deprecated kwargs
|
||||||
image_processor = self.image_processing_class(**image_processor_dict)
|
for image_processing_class in self.image_processor_list:
|
||||||
self.assertEqual(image_processor.do_reduce_labels, True)
|
image_processor = image_processing_class(**image_processor_dict)
|
||||||
|
self.assertEqual(image_processor.do_reduce_labels, True)
|
||||||
|
|
||||||
# test we still support reduce_labels with config
|
# test we still support reduce_labels with config
|
||||||
image_processor = self.image_processing_class.from_dict(image_processor_dict)
|
image_processor = image_processing_class.from_dict(image_processor_dict)
|
||||||
self.assertEqual(image_processor.do_reduce_labels, True)
|
self.assertEqual(image_processor.do_reduce_labels, True)
|
||||||
|
|
||||||
|
def test_slow_fast_equivalence(self):
|
||||||
|
if not self.test_slow_image_processor or not self.test_fast_image_processor:
|
||||||
|
self.skipTest(reason="Skipping slow/fast equivalence test")
|
||||||
|
|
||||||
|
if self.image_processing_class is None or self.fast_image_processing_class is None:
|
||||||
|
self.skipTest(reason="Skipping slow/fast equivalence test as one of the image processors is not defined")
|
||||||
|
|
||||||
|
dummy_image, dummy_map = prepare_semantic_single_inputs()
|
||||||
|
|
||||||
|
image_processor_slow = self.image_processing_class(**self.image_processor_dict)
|
||||||
|
image_processor_fast = self.fast_image_processing_class(**self.image_processor_dict)
|
||||||
|
|
||||||
|
image_encoding_slow = image_processor_slow(dummy_image, segmentation_maps=dummy_map, return_tensors="pt")
|
||||||
|
image_encoding_fast = image_processor_fast(dummy_image, segmentation_maps=dummy_map, return_tensors="pt")
|
||||||
|
self._assert_slow_fast_tensors_equivalence(image_encoding_slow.pixel_values, image_encoding_fast.pixel_values)
|
||||||
|
for mask_label_slow, mask_label_fast in zip(image_encoding_slow.mask_labels, image_encoding_fast.mask_labels):
|
||||||
|
self._assert_slow_fast_tensors_equivalence(mask_label_slow, mask_label_fast)
|
||||||
|
for class_label_slow, class_label_fast in zip(
|
||||||
|
image_encoding_slow.class_labels, image_encoding_fast.class_labels
|
||||||
|
):
|
||||||
|
self._assert_slow_fast_tensors_equivalence(class_label_slow.float(), class_label_fast.float())
|
||||||
|
|
||||||
|
def test_slow_fast_equivalence_batched(self):
|
||||||
|
if not self.test_slow_image_processor or not self.test_fast_image_processor:
|
||||||
|
self.skipTest(reason="Skipping slow/fast equivalence test")
|
||||||
|
|
||||||
|
if self.image_processing_class is None or self.fast_image_processing_class is None:
|
||||||
|
self.skipTest(reason="Skipping slow/fast equivalence test as one of the image processors is not defined")
|
||||||
|
|
||||||
|
if hasattr(self.image_processor_tester, "do_center_crop") and self.image_processor_tester.do_center_crop:
|
||||||
|
self.skipTest(
|
||||||
|
reason="Skipping as do_center_crop is True and center_crop functions are not equivalent for fast and slow processors"
|
||||||
|
)
|
||||||
|
|
||||||
|
dummy_images, dummy_maps = prepare_semantic_batch_inputs()
|
||||||
|
|
||||||
|
image_processor_slow = self.image_processing_class(**self.image_processor_dict)
|
||||||
|
image_processor_fast = self.fast_image_processing_class(**self.image_processor_dict)
|
||||||
|
|
||||||
|
encoding_slow = image_processor_slow(dummy_images, segmentation_maps=dummy_maps, return_tensors="pt")
|
||||||
|
encoding_fast = image_processor_fast(dummy_images, segmentation_maps=dummy_maps, return_tensors="pt")
|
||||||
|
|
||||||
|
self._assert_slow_fast_tensors_equivalence(encoding_slow.pixel_values, encoding_fast.pixel_values)
|
||||||
|
for mask_label_slow, mask_label_fast in zip(encoding_slow.mask_labels, encoding_fast.mask_labels):
|
||||||
|
self._assert_slow_fast_tensors_equivalence(mask_label_slow, mask_label_fast)
|
||||||
|
for class_label_slow, class_label_fast in zip(encoding_slow.class_labels, encoding_fast.class_labels):
|
||||||
|
self._assert_slow_fast_tensors_equivalence(class_label_slow.float(), class_label_fast.float())
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ from datasets import load_dataset
|
|||||||
from huggingface_hub import hf_hub_download
|
from huggingface_hub import hf_hub_download
|
||||||
|
|
||||||
from transformers.testing_utils import require_torch, require_vision
|
from transformers.testing_utils import require_torch, require_vision
|
||||||
from transformers.utils import is_torch_available, is_vision_available
|
from transformers.utils import is_torch_available, is_torchvision_available, is_vision_available
|
||||||
|
|
||||||
from ...test_image_processing_common import ImageProcessingTestMixin, prepare_image_inputs
|
from ...test_image_processing_common import ImageProcessingTestMixin, prepare_image_inputs
|
||||||
|
|
||||||
@@ -33,6 +33,9 @@ if is_torch_available():
|
|||||||
from transformers.models.maskformer.image_processing_maskformer import binary_mask_to_rle
|
from transformers.models.maskformer.image_processing_maskformer import binary_mask_to_rle
|
||||||
from transformers.models.maskformer.modeling_maskformer import MaskFormerForInstanceSegmentationOutput
|
from transformers.models.maskformer.modeling_maskformer import MaskFormerForInstanceSegmentationOutput
|
||||||
|
|
||||||
|
if is_torchvision_available():
|
||||||
|
from transformers import MaskFormerImageProcessorFast
|
||||||
|
|
||||||
if is_vision_available():
|
if is_vision_available():
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
@@ -144,10 +147,26 @@ class MaskFormerImageProcessingTester:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# Copied from transformers.tests.models.beit.test_image_processing_beit.prepare_semantic_single_inputs
|
||||||
|
def prepare_semantic_single_inputs():
|
||||||
|
ds = load_dataset("hf-internal-testing/fixtures_ade20k", split="test")
|
||||||
|
example = ds[0]
|
||||||
|
return example["image"], example["map"]
|
||||||
|
|
||||||
|
|
||||||
|
# Copied from transformers.tests.models.beit.test_image_processing_beit.prepare_semantic_batch_inputs
|
||||||
|
def prepare_semantic_batch_inputs():
|
||||||
|
ds = load_dataset("hf-internal-testing/fixtures_ade20k", split="test")
|
||||||
|
return list(ds["image"][:2]), list(ds["map"][:2])
|
||||||
|
|
||||||
|
|
||||||
@require_torch
|
@require_torch
|
||||||
@require_vision
|
@require_vision
|
||||||
class MaskFormerImageProcessingTest(ImageProcessingTestMixin, unittest.TestCase):
|
class MaskFormerImageProcessingTest(ImageProcessingTestMixin, unittest.TestCase):
|
||||||
image_processing_class = MaskFormerImageProcessor if (is_vision_available() and is_torch_available()) else None
|
image_processing_class = MaskFormerImageProcessor if (is_vision_available() and is_torch_available()) else None
|
||||||
|
fast_image_processing_class = (
|
||||||
|
MaskFormerImageProcessorFast if (is_vision_available() and is_torchvision_available()) else None
|
||||||
|
)
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super().setUp()
|
super().setUp()
|
||||||
@@ -158,25 +177,27 @@ class MaskFormerImageProcessingTest(ImageProcessingTestMixin, unittest.TestCase)
|
|||||||
return self.image_processor_tester.prepare_image_processor_dict()
|
return self.image_processor_tester.prepare_image_processor_dict()
|
||||||
|
|
||||||
def test_image_processor_properties(self):
|
def test_image_processor_properties(self):
|
||||||
image_processing = self.image_processing_class(**self.image_processor_dict)
|
for image_processing_class in self.image_processor_list:
|
||||||
self.assertTrue(hasattr(image_processing, "image_mean"))
|
image_processing = image_processing_class(**self.image_processor_dict)
|
||||||
self.assertTrue(hasattr(image_processing, "image_std"))
|
self.assertTrue(hasattr(image_processing, "image_mean"))
|
||||||
self.assertTrue(hasattr(image_processing, "do_normalize"))
|
self.assertTrue(hasattr(image_processing, "image_std"))
|
||||||
self.assertTrue(hasattr(image_processing, "do_resize"))
|
self.assertTrue(hasattr(image_processing, "do_normalize"))
|
||||||
self.assertTrue(hasattr(image_processing, "size"))
|
self.assertTrue(hasattr(image_processing, "do_resize"))
|
||||||
self.assertTrue(hasattr(image_processing, "ignore_index"))
|
self.assertTrue(hasattr(image_processing, "size"))
|
||||||
self.assertTrue(hasattr(image_processing, "num_labels"))
|
self.assertTrue(hasattr(image_processing, "ignore_index"))
|
||||||
|
self.assertTrue(hasattr(image_processing, "num_labels"))
|
||||||
|
|
||||||
def test_image_processor_from_dict_with_kwargs(self):
|
def test_image_processor_from_dict_with_kwargs(self):
|
||||||
image_processor = self.image_processing_class.from_dict(self.image_processor_dict)
|
for image_processing_class in self.image_processor_list:
|
||||||
self.assertEqual(image_processor.size, {"shortest_edge": 32, "longest_edge": 1333})
|
image_processor = image_processing_class.from_dict(self.image_processor_dict)
|
||||||
self.assertEqual(image_processor.size_divisor, 0)
|
self.assertEqual(image_processor.size, {"shortest_edge": 32, "longest_edge": 1333})
|
||||||
|
self.assertEqual(image_processor.size_divisor, 0)
|
||||||
|
|
||||||
image_processor = self.image_processing_class.from_dict(
|
image_processor = image_processing_class.from_dict(
|
||||||
self.image_processor_dict, size=42, max_size=84, size_divisibility=8
|
self.image_processor_dict, size=42, max_size=84, size_divisibility=8
|
||||||
)
|
)
|
||||||
self.assertEqual(image_processor.size, {"shortest_edge": 42, "longest_edge": 84})
|
self.assertEqual(image_processor.size, {"shortest_edge": 42, "longest_edge": 84})
|
||||||
self.assertEqual(image_processor.size_divisor, 8)
|
self.assertEqual(image_processor.size_divisor, 8)
|
||||||
|
|
||||||
def comm_get_image_processing_inputs(
|
def comm_get_image_processing_inputs(
|
||||||
self, with_segmentation_maps=False, is_instance_map=False, segmentation_type="np"
|
self, with_segmentation_maps=False, is_instance_map=False, segmentation_type="np"
|
||||||
@@ -211,15 +232,16 @@ class MaskFormerImageProcessingTest(ImageProcessingTestMixin, unittest.TestCase)
|
|||||||
def test_with_size_divisor(self):
|
def test_with_size_divisor(self):
|
||||||
size_divisors = [8, 16, 32]
|
size_divisors = [8, 16, 32]
|
||||||
weird_input_sizes = [(407, 802), (582, 1094)]
|
weird_input_sizes = [(407, 802), (582, 1094)]
|
||||||
for size_divisor in size_divisors:
|
for image_processing_class in self.image_processor_list:
|
||||||
image_processor_dict = {**self.image_processor_dict, **{"size_divisor": size_divisor}}
|
for size_divisor in size_divisors:
|
||||||
image_processing = self.image_processing_class(**image_processor_dict)
|
image_processor_dict = {**self.image_processor_dict, **{"size_divisor": size_divisor}}
|
||||||
for weird_input_size in weird_input_sizes:
|
image_processing = image_processing_class(**image_processor_dict)
|
||||||
inputs = image_processing([np.ones((3, *weird_input_size))], return_tensors="pt")
|
for weird_input_size in weird_input_sizes:
|
||||||
pixel_values = inputs["pixel_values"]
|
inputs = image_processing([np.ones((3, *weird_input_size))], return_tensors="pt")
|
||||||
# check if divisible
|
pixel_values = inputs["pixel_values"]
|
||||||
self.assertTrue((pixel_values.shape[-1] % size_divisor) == 0)
|
# check if divisible
|
||||||
self.assertTrue((pixel_values.shape[-2] % size_divisor) == 0)
|
self.assertTrue((pixel_values.shape[-1] % size_divisor) == 0)
|
||||||
|
self.assertTrue((pixel_values.shape[-2] % size_divisor) == 0)
|
||||||
|
|
||||||
def test_call_with_segmentation_maps(self):
|
def test_call_with_segmentation_maps(self):
|
||||||
def common(is_instance_map=False, segmentation_type=None):
|
def common(is_instance_map=False, segmentation_type=None):
|
||||||
@@ -417,116 +439,122 @@ class MaskFormerImageProcessingTest(ImageProcessingTestMixin, unittest.TestCase)
|
|||||||
self.assertEqual(rle[1], 45)
|
self.assertEqual(rle[1], 45)
|
||||||
|
|
||||||
def test_post_process_segmentation(self):
|
def test_post_process_segmentation(self):
|
||||||
fature_extractor = self.image_processing_class(num_labels=self.image_processor_tester.num_classes)
|
for image_processing_class in self.image_processor_list:
|
||||||
outputs = self.image_processor_tester.get_fake_maskformer_outputs()
|
feature_extractor = image_processing_class(num_labels=self.image_processor_tester.num_classes)
|
||||||
segmentation = fature_extractor.post_process_segmentation(outputs)
|
outputs = self.image_processor_tester.get_fake_maskformer_outputs()
|
||||||
|
segmentation = feature_extractor.post_process_segmentation(outputs)
|
||||||
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
segmentation.shape,
|
segmentation.shape,
|
||||||
(
|
(
|
||||||
self.image_processor_tester.batch_size,
|
self.image_processor_tester.batch_size,
|
||||||
self.image_processor_tester.num_classes,
|
self.image_processor_tester.num_classes,
|
||||||
self.image_processor_tester.height,
|
self.image_processor_tester.height,
|
||||||
self.image_processor_tester.width,
|
self.image_processor_tester.width,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
target_size = (1, 4)
|
target_size = (1, 4)
|
||||||
segmentation = fature_extractor.post_process_segmentation(outputs, target_size=target_size)
|
segmentation = feature_extractor.post_process_segmentation(outputs, target_size=target_size)
|
||||||
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
segmentation.shape,
|
segmentation.shape,
|
||||||
(self.image_processor_tester.batch_size, self.image_processor_tester.num_classes, *target_size),
|
(self.image_processor_tester.batch_size, self.image_processor_tester.num_classes, *target_size),
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_post_process_semantic_segmentation(self):
|
def test_post_process_semantic_segmentation(self):
|
||||||
fature_extractor = self.image_processing_class(num_labels=self.image_processor_tester.num_classes)
|
for image_processing_class in self.image_processor_list:
|
||||||
outputs = self.image_processor_tester.get_fake_maskformer_outputs()
|
feature_extractor = image_processing_class(num_labels=self.image_processor_tester.num_classes)
|
||||||
|
outputs = self.image_processor_tester.get_fake_maskformer_outputs()
|
||||||
|
|
||||||
segmentation = fature_extractor.post_process_semantic_segmentation(outputs)
|
segmentation = feature_extractor.post_process_semantic_segmentation(outputs)
|
||||||
|
|
||||||
self.assertEqual(len(segmentation), self.image_processor_tester.batch_size)
|
self.assertEqual(len(segmentation), self.image_processor_tester.batch_size)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
segmentation[0].shape,
|
segmentation[0].shape,
|
||||||
(
|
(
|
||||||
self.image_processor_tester.height,
|
self.image_processor_tester.height,
|
||||||
self.image_processor_tester.width,
|
self.image_processor_tester.width,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
target_sizes = [(1, 4) for i in range(self.image_processor_tester.batch_size)]
|
target_sizes = [(1, 4) for i in range(self.image_processor_tester.batch_size)]
|
||||||
segmentation = fature_extractor.post_process_semantic_segmentation(outputs, target_sizes=target_sizes)
|
segmentation = feature_extractor.post_process_semantic_segmentation(outputs, target_sizes=target_sizes)
|
||||||
|
|
||||||
self.assertEqual(segmentation[0].shape, target_sizes[0])
|
self.assertEqual(segmentation[0].shape, target_sizes[0])
|
||||||
|
|
||||||
def test_post_process_instance_segmentation(self):
|
def test_post_process_instance_segmentation(self):
|
||||||
image_processor = self.image_processing_class(num_labels=self.image_processor_tester.num_classes)
|
for image_processing_class in self.image_processor_list:
|
||||||
outputs = self.image_processor_tester.get_fake_maskformer_outputs()
|
image_processor = image_processing_class(num_labels=self.image_processor_tester.num_classes)
|
||||||
segmentation = image_processor.post_process_instance_segmentation(outputs, threshold=0)
|
outputs = self.image_processor_tester.get_fake_maskformer_outputs()
|
||||||
|
segmentation = image_processor.post_process_instance_segmentation(outputs, threshold=0)
|
||||||
|
|
||||||
self.assertTrue(len(segmentation) == self.image_processor_tester.batch_size)
|
self.assertTrue(len(segmentation) == self.image_processor_tester.batch_size)
|
||||||
for el in segmentation:
|
for el in segmentation:
|
||||||
self.assertTrue("segmentation" in el)
|
self.assertTrue("segmentation" in el)
|
||||||
self.assertTrue("segments_info" in el)
|
self.assertTrue("segments_info" in el)
|
||||||
self.assertEqual(type(el["segments_info"]), list)
|
self.assertEqual(type(el["segments_info"]), list)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
el["segmentation"].shape, (self.image_processor_tester.height, self.image_processor_tester.width)
|
el["segmentation"].shape, (self.image_processor_tester.height, self.image_processor_tester.width)
|
||||||
|
)
|
||||||
|
|
||||||
|
segmentation = image_processor.post_process_instance_segmentation(
|
||||||
|
outputs, threshold=0, return_binary_maps=True
|
||||||
)
|
)
|
||||||
|
|
||||||
segmentation = image_processor.post_process_instance_segmentation(
|
self.assertTrue(len(segmentation) == self.image_processor_tester.batch_size)
|
||||||
outputs, threshold=0, return_binary_maps=True
|
for el in segmentation:
|
||||||
)
|
self.assertTrue("segmentation" in el)
|
||||||
|
self.assertTrue("segments_info" in el)
|
||||||
self.assertTrue(len(segmentation) == self.image_processor_tester.batch_size)
|
self.assertEqual(type(el["segments_info"]), list)
|
||||||
for el in segmentation:
|
self.assertEqual(len(el["segmentation"].shape), 3)
|
||||||
self.assertTrue("segmentation" in el)
|
self.assertEqual(
|
||||||
self.assertTrue("segments_info" in el)
|
el["segmentation"].shape[1:],
|
||||||
self.assertEqual(type(el["segments_info"]), list)
|
(self.image_processor_tester.height, self.image_processor_tester.width),
|
||||||
self.assertEqual(len(el["segmentation"].shape), 3)
|
)
|
||||||
self.assertEqual(
|
|
||||||
el["segmentation"].shape[1:], (self.image_processor_tester.height, self.image_processor_tester.width)
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_post_process_panoptic_segmentation(self):
|
def test_post_process_panoptic_segmentation(self):
|
||||||
image_processing = self.image_processing_class(num_labels=self.image_processor_tester.num_classes)
|
for image_processing_class in self.image_processor_list:
|
||||||
outputs = self.image_processor_tester.get_fake_maskformer_outputs()
|
image_processing = image_processing_class(num_labels=self.image_processor_tester.num_classes)
|
||||||
segmentation = image_processing.post_process_panoptic_segmentation(outputs, threshold=0)
|
outputs = self.image_processor_tester.get_fake_maskformer_outputs()
|
||||||
|
segmentation = image_processing.post_process_panoptic_segmentation(outputs, threshold=0)
|
||||||
|
|
||||||
self.assertTrue(len(segmentation) == self.image_processor_tester.batch_size)
|
self.assertTrue(len(segmentation) == self.image_processor_tester.batch_size)
|
||||||
for el in segmentation:
|
for el in segmentation:
|
||||||
self.assertTrue("segmentation" in el)
|
self.assertTrue("segmentation" in el)
|
||||||
self.assertTrue("segments_info" in el)
|
self.assertTrue("segments_info" in el)
|
||||||
self.assertEqual(type(el["segments_info"]), list)
|
self.assertEqual(type(el["segments_info"]), list)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
el["segmentation"].shape, (self.image_processor_tester.height, self.image_processor_tester.width)
|
el["segmentation"].shape, (self.image_processor_tester.height, self.image_processor_tester.width)
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_post_process_label_fusing(self):
|
def test_post_process_label_fusing(self):
|
||||||
image_processor = self.image_processing_class(num_labels=self.image_processor_tester.num_classes)
|
for image_processing_class in self.image_processor_list:
|
||||||
outputs = self.image_processor_tester.get_fake_maskformer_outputs()
|
image_processor = self.image_processing_class(num_labels=self.image_processor_tester.num_classes)
|
||||||
|
outputs = self.image_processor_tester.get_fake_maskformer_outputs()
|
||||||
|
|
||||||
segmentation = image_processor.post_process_panoptic_segmentation(
|
segmentation = image_processor.post_process_panoptic_segmentation(
|
||||||
outputs, threshold=0, mask_threshold=0, overlap_mask_area_threshold=0
|
outputs, threshold=0, mask_threshold=0, overlap_mask_area_threshold=0
|
||||||
)
|
)
|
||||||
unfused_segments = [el["segments_info"] for el in segmentation]
|
unfused_segments = [el["segments_info"] for el in segmentation]
|
||||||
|
|
||||||
fused_segmentation = image_processor.post_process_panoptic_segmentation(
|
fused_segmentation = image_processor.post_process_panoptic_segmentation(
|
||||||
outputs, threshold=0, mask_threshold=0, overlap_mask_area_threshold=0, label_ids_to_fuse={1}
|
outputs, threshold=0, mask_threshold=0, overlap_mask_area_threshold=0, label_ids_to_fuse={1}
|
||||||
)
|
)
|
||||||
fused_segments = [el["segments_info"] for el in fused_segmentation]
|
fused_segments = [el["segments_info"] for el in fused_segmentation]
|
||||||
|
|
||||||
for el_unfused, el_fused in zip(unfused_segments, fused_segments):
|
for el_unfused, el_fused in zip(unfused_segments, fused_segments):
|
||||||
if len(el_unfused) == 0:
|
if len(el_unfused) == 0:
|
||||||
self.assertEqual(len(el_unfused), len(el_fused))
|
self.assertEqual(len(el_unfused), len(el_fused))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Get number of segments to be fused
|
# Get number of segments to be fused
|
||||||
fuse_targets = [1 for el in el_unfused if el["label_id"] in {1}]
|
fuse_targets = [1 for el in el_unfused if el["label_id"] in {1}]
|
||||||
num_to_fuse = 0 if len(fuse_targets) == 0 else sum(fuse_targets) - 1
|
num_to_fuse = 0 if len(fuse_targets) == 0 else sum(fuse_targets) - 1
|
||||||
# Expected number of segments after fusing
|
# Expected number of segments after fusing
|
||||||
expected_num_segments = max([el["id"] for el in el_unfused]) - num_to_fuse
|
expected_num_segments = max([el["id"] for el in el_unfused]) - num_to_fuse
|
||||||
num_segments_fused = max([el["id"] for el in el_fused])
|
num_segments_fused = max([el["id"] for el in el_fused])
|
||||||
self.assertEqual(num_segments_fused, expected_num_segments)
|
self.assertEqual(num_segments_fused, expected_num_segments)
|
||||||
|
|
||||||
def test_removed_deprecated_kwargs(self):
|
def test_removed_deprecated_kwargs(self):
|
||||||
image_processor_dict = dict(self.image_processor_dict)
|
image_processor_dict = dict(self.image_processor_dict)
|
||||||
@@ -540,3 +568,50 @@ class MaskFormerImageProcessingTest(ImageProcessingTestMixin, unittest.TestCase)
|
|||||||
# test we still support reduce_labels with config
|
# test we still support reduce_labels with config
|
||||||
image_processor = self.image_processing_class.from_dict(image_processor_dict)
|
image_processor = self.image_processing_class.from_dict(image_processor_dict)
|
||||||
self.assertEqual(image_processor.do_reduce_labels, True)
|
self.assertEqual(image_processor.do_reduce_labels, True)
|
||||||
|
|
||||||
|
def test_slow_fast_equivalence(self):
|
||||||
|
if not self.test_slow_image_processor or not self.test_fast_image_processor:
|
||||||
|
self.skipTest(reason="Skipping slow/fast equivalence test")
|
||||||
|
|
||||||
|
if self.image_processing_class is None or self.fast_image_processing_class is None:
|
||||||
|
self.skipTest(reason="Skipping slow/fast equivalence test as one of the image processors is not defined")
|
||||||
|
|
||||||
|
dummy_image, dummy_map = prepare_semantic_single_inputs()
|
||||||
|
|
||||||
|
image_processor_slow = self.image_processing_class(**self.image_processor_dict)
|
||||||
|
image_processor_fast = self.fast_image_processing_class(**self.image_processor_dict)
|
||||||
|
|
||||||
|
image_encoding_slow = image_processor_slow(dummy_image, segmentation_maps=dummy_map, return_tensors="pt")
|
||||||
|
image_encoding_fast = image_processor_fast(dummy_image, segmentation_maps=dummy_map, return_tensors="pt")
|
||||||
|
self._assert_slow_fast_tensors_equivalence(image_encoding_slow.pixel_values, image_encoding_fast.pixel_values)
|
||||||
|
for mask_label_slow, mask_label_fast in zip(image_encoding_slow.mask_labels, image_encoding_fast.mask_labels):
|
||||||
|
self._assert_slow_fast_tensors_equivalence(mask_label_slow, mask_label_fast)
|
||||||
|
for class_label_slow, class_label_fast in zip(
|
||||||
|
image_encoding_slow.class_labels, image_encoding_fast.class_labels
|
||||||
|
):
|
||||||
|
self._assert_slow_fast_tensors_equivalence(class_label_slow.float(), class_label_fast.float())
|
||||||
|
|
||||||
|
def test_slow_fast_equivalence_batched(self):
|
||||||
|
if not self.test_slow_image_processor or not self.test_fast_image_processor:
|
||||||
|
self.skipTest(reason="Skipping slow/fast equivalence test")
|
||||||
|
|
||||||
|
if self.image_processing_class is None or self.fast_image_processing_class is None:
|
||||||
|
self.skipTest(reason="Skipping slow/fast equivalence test as one of the image processors is not defined")
|
||||||
|
|
||||||
|
if hasattr(self.image_processor_tester, "do_center_crop") and self.image_processor_tester.do_center_crop:
|
||||||
|
self.skipTest(
|
||||||
|
reason="Skipping as do_center_crop is True and center_crop functions are not equivalent for fast and slow processors"
|
||||||
|
)
|
||||||
|
|
||||||
|
dummy_images, dummy_maps = prepare_semantic_batch_inputs()
|
||||||
|
|
||||||
|
image_processor_slow = self.image_processing_class(**self.image_processor_dict)
|
||||||
|
image_processor_fast = self.fast_image_processing_class(**self.image_processor_dict)
|
||||||
|
|
||||||
|
encoding_slow = image_processor_slow(dummy_images, segmentation_maps=dummy_maps, return_tensors="pt")
|
||||||
|
encoding_fast = image_processor_fast(dummy_images, segmentation_maps=dummy_maps, return_tensors="pt")
|
||||||
|
self._assert_slow_fast_tensors_equivalence(encoding_slow.pixel_values, encoding_fast.pixel_values)
|
||||||
|
for mask_label_slow, mask_label_fast in zip(encoding_slow.mask_labels, encoding_fast.mask_labels):
|
||||||
|
self._assert_slow_fast_tensors_equivalence(mask_label_slow, mask_label_fast)
|
||||||
|
for class_label_slow, class_label_fast in zip(encoding_slow.class_labels, encoding_fast.class_labels):
|
||||||
|
self._assert_slow_fast_tensors_equivalence(class_label_slow.float(), class_label_fast.float())
|
||||||
|
|||||||
Reference in New Issue
Block a user