!pip install numpy pillow tqdm torch transformers datasets accelerate
import os
import numpy as np
from PIL import Image
from tqdm import tqdm
import json
import torch
import torch.nn as nn
from datasets import load_dataset
from transformers import CLIPProcessor, CLIPModel
from typing import Optional
from transformers.models.clip.modeling_clip import CLIPVisionTransformer, CLIPVisionConfig, BaseModelOutputWithPooling
DATASET_PATH = "IOAI-official/IOAI-2025-Pixel-test"
SPLIT = "test"
MODEL_PATH = "openai/clip-vit-large-patch14"
DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
BACKGROUND_CLASS = "other"
HEIGHT = 224
WIDTH = 224
RETAIN_RATIO = 0.0625
MEAN_COLOR = (0, 0, 0)
STRIDE = 2
TOP_K = 3
print("Loading dataset...")
dataset = load_dataset(DATASET_PATH, split=SPLIT)
print(f"Dataset loaded successfully! Total samples: {len(dataset)}")
print(f"Loading CLIP model and processor: {MODEL_PATH}...")
model = CLIPModel.from_pretrained(MODEL_PATH).to(DEVICE)
processor = CLIPProcessor.from_pretrained(MODEL_PATH)
print("Model and processor loaded successfully.")
def generate_all_rectangular_regions(image_size=224, patch_size=14, retain_ratio=RETAIN_RATIO, max_aspect_ratio=1.2, stride=1):
"""Generate rectangular regions with optimizations for speed"""
max_pixels = int(retain_ratio * image_size * image_size)
patches_per_side = image_size // patch_size
patch_area = patch_size * patch_size
target_patches = max_pixels // patch_area
min_patches = max(1, target_patches - 1)
max_patches = target_patches + 1
regions = []
region_to_patches = []
valid_dims = []
for width_patches in range(1, patches_per_side + 1):
for height_patches in range(1, patches_per_side + 1):
total_patches = width_patches * height_patches
if min_patches <= total_patches <= max_patches:
aspect_ratio = max(width_patches, height_patches) / min(width_patches, height_patches)
if aspect_ratio <= max_aspect_ratio:
valid_dims.append((width_patches, height_patches, total_patches))
for width_patches, height_patches, total_patches in valid_dims:
for top_patch in range(0, patches_per_side - height_patches + 1, stride):
for left_patch in range(0, patches_per_side - width_patches + 1, stride):
bottom_patch = top_patch + height_patches
right_patch = left_patch + width_patches
pixel_coords = (
top_patch * patch_size,
left_patch * patch_size,
bottom_patch * patch_size,
right_patch * patch_size
)
regions.append(pixel_coords)
covered_patches = []
for p_row in range(top_patch, bottom_patch):
for p_col in range(left_patch, right_patch):
patch_idx = p_row * patches_per_side + p_col
covered_patches.append(patch_idx)
region_to_patches.append(covered_patches)
return regions, region_to_patches
class MaskCLIPVisionTransformer(CLIPVisionTransformer):
"""Modified CLIP Vision Transformer that supports mask tokens for all possible rectangular regions"""
def __init__(self, config: CLIPVisionConfig, retain_ratio=RETAIN_RATIO):
super().__init__(config)
self.retain_ratio = retain_ratio
self.num_patches = (config.image_size // config.patch_size) ** 2
self.regions, self.region_to_patches = generate_all_rectangular_regions(
image_size=config.image_size,
patch_size=config.patch_size,
retain_ratio=retain_ratio,
max_aspect_ratio=1.2,
stride=STRIDE
)
self.num_mask_tokens = len(self.regions)
self.mask_tokens = nn.Parameter(torch.randn(1, self.num_mask_tokens, config.hidden_size))
def create_mask_attention_matrix(self, batch_size):
"""Create attention mask matrix for all rectangular regions"""
N = self.num_patches
M = self.num_mask_tokens
total_tokens = N + 1 + M
attention_mask = torch.zeros(total_tokens, total_tokens, dtype=torch.bool, device=self.mask_tokens.device)
attention_mask[:N+1, N+1:] = True
attention_mask[N+1:, 1:N+1] = True
for mask_idx in range(M):
covered_patches = self.region_to_patches[mask_idx]
for patch_idx in covered_patches:
token_pos = 1 + patch_idx
attention_mask[N + 1 + mask_idx, token_pos] = False
attention_mask[N+1:, N+1:] = True
for i in range(M):
attention_mask[N + 1 + i, N + 1 + i] = False
return attention_mask
def forward(
self,
pixel_values: Optional[torch.FloatTensor] = None,
output_attentions: Optional[bool] = None,
output_hidden_states: Optional[bool] = None,
interpolate_pos_encoding: Optional[bool] = False,
use_mask_tokens: bool = False,
) -> BaseModelOutputWithPooling:
"""Forward pass with optional mask tokens"""
output_attentions = output_attentions if output_attentions is not None else self.config.output_attentions
output_hidden_states = (
output_hidden_states if output_hidden_states is not None else self.config.output_hidden_states
)
if pixel_values is None:
raise ValueError("You have to specify pixel_values")
hidden_states = self.embeddings(pixel_values, interpolate_pos_encoding=interpolate_pos_encoding)
hidden_states = self.pre_layrnorm(hidden_states)
if use_mask_tokens:
batch_size = hidden_states.shape[0]
cls_token_embedding = hidden_states[:, 0:1, :]
mask_tokens_expanded = cls_token_embedding.expand(batch_size, self.num_mask_tokens, -1)
if mask_tokens_expanded.device != hidden_states.device:
mask_tokens_expanded = mask_tokens_expanded.to(hidden_states.device)
hidden_states = torch.cat([hidden_states, mask_tokens_expanded], dim=1)
attention_mask = self.create_mask_attention_matrix(batch_size)
seq_len = hidden_states.shape[1]
attention_mask_4d = attention_mask.unsqueeze(0).unsqueeze(0).expand(batch_size, 1, -1, -1)
attention_mask_4d = attention_mask_4d.float()
attention_mask_4d = attention_mask_4d.masked_fill(attention_mask_4d == 1, float('-inf'))
attention_mask_4d = attention_mask_4d.masked_fill(attention_mask_4d == 0, 0.0)
else:
attention_mask_4d = None
encoder_outputs = self.encoder(
inputs_embeds=hidden_states,
attention_mask=attention_mask_4d,
causal_attention_mask=None,
output_attentions=output_attentions,
output_hidden_states=output_hidden_states,
)
last_hidden_state = encoder_outputs.last_hidden_state
if use_mask_tokens:
class_token_output = last_hidden_state[:, 0]
mask_tokens_output = last_hidden_state[:, self.num_patches + 1:]
pooled_output = self.post_layernorm(class_token_output)
mask_tokens_output = self.post_layernorm(mask_tokens_output)
return {
'last_hidden_state': last_hidden_state,
'pooler_output': pooled_output,
'mask_tokens_output': mask_tokens_output,
'hidden_states': encoder_outputs.hidden_states,
'attentions': encoder_outputs.attentions,
}
else:
pooled_output = last_hidden_state[:, 0, :]
pooled_output = self.post_layernorm(pooled_output)
return BaseModelOutputWithPooling(
last_hidden_state=last_hidden_state,
pooler_output=pooled_output,
hidden_states=encoder_outputs.hidden_states,
attentions=encoder_outputs.attentions,
)
def apply_mask_with_mean(image, mask, mean_rgb=MEAN_COLOR):
"""Apply arbitrary binary mask to image, replacing masked areas with mean values"""
img_array = np.array(image).copy()
if isinstance(mask, Image.Image):
mask_array = np.array(mask.convert('L')) > 127
else:
mask_array = mask > 0
mask_3d = np.stack([mask_array] * 3, axis=2)
mean_values = np.array([int(m * 255) for m in mean_rgb])
img_array = np.where(mask_3d, img_array, mean_values.reshape(1, 1, 3))
return Image.fromarray(img_array.astype(np.uint8))
def compute_vision_features_once(model, image, mask_vision_model):
"""
Compute vision features once for efficient reuse across multiple mask selection functions.
This eliminates redundant forward passes.
"""
image_inputs = processor(images=image, return_tensors="pt").to(DEVICE)
with torch.no_grad():
vision_outputs = mask_vision_model(
pixel_values=image_inputs['pixel_values'],
use_mask_tokens=True
)
full_image_features = vision_outputs['pooler_output']
if hasattr(model, 'visual_projection') and model.visual_projection is not None:
full_image_features = model.visual_projection(full_image_features)
mask_tokens_features = vision_outputs['mask_tokens_output']
if hasattr(model, 'visual_projection') and model.visual_projection is not None:
batch_size, num_tokens, embed_dim = mask_tokens_features.shape
mask_tokens_features = mask_tokens_features.view(-1, embed_dim)
mask_tokens_features = model.visual_projection(mask_tokens_features)
mask_tokens_features = mask_tokens_features.view(batch_size, num_tokens, -1)
full_image_features = full_image_features / full_image_features.norm(dim=-1, keepdim=True)
mask_tokens_features = mask_tokens_features / mask_tokens_features.norm(dim=-1, keepdim=True)
return vision_outputs, full_image_features, mask_tokens_features
def find_best_mask_region_calibrated(model, image, class_names, mask_vision_model, text_features,
vision_outputs=None, full_image_features=None, mask_tokens_features=None,
return_detailed=False):
"""Find the best mask region using MaskCLIP approach with calibration for black-pixel masking"""
num_mask_tokens = mask_vision_model.num_mask_tokens
if vision_outputs is None or full_image_features is None or mask_tokens_features is None:
image_inputs = processor(images=image, return_tensors="pt").to(DEVICE)
with torch.no_grad():
vision_outputs = mask_vision_model(
pixel_values=image_inputs['pixel_values'],
use_mask_tokens=True
)
full_image_features = vision_outputs['pooler_output']
if hasattr(model, 'visual_projection') and model.visual_projection is not None:
full_image_features = model.visual_projection(full_image_features)
mask_tokens_features = vision_outputs['mask_tokens_output']
if hasattr(model, 'visual_projection') and model.visual_projection is not None:
batch_size, num_tokens, embed_dim = mask_tokens_features.shape
mask_tokens_features = mask_tokens_features.view(-1, embed_dim)
mask_tokens_features = model.visual_projection(mask_tokens_features)
mask_tokens_features = mask_tokens_features.view(batch_size, num_tokens, -1)
full_image_features = full_image_features / full_image_features.norm(dim=-1, keepdim=True)
mask_tokens_features = mask_tokens_features / mask_tokens_features.norm(dim=-1, keepdim=True)
full_image_similarities = torch.matmul(full_image_features, text_features.T)
full_image_prediction = torch.argmax(full_image_similarities, dim=-1)
predicted_class_idx = full_image_prediction.item()
mask_similarities = torch.matmul(mask_tokens_features.squeeze(0), text_features.T)
mask_predictions = torch.argmax(mask_similarities, dim=-1)
matching_masks = (mask_predictions == predicted_class_idx)
if matching_masks.any():
candidate_indices = torch.where(matching_masks)[0]
candidate_confidences = mask_similarities[candidate_indices, predicted_class_idx]
sorted_indices = torch.argsort(candidate_confidences, descending=True)
sorted_candidates = candidate_indices[sorted_indices]
else:
candidate_confidences = mask_similarities[:, predicted_class_idx]
sorted_candidates = torch.topk(candidate_confidences, len(candidate_confidences)).indices
if TOP_K == 1:
return sorted_candidates[0].item()
calibration_results = []
candidates_to_test = sorted_candidates[:TOP_K]
for i, candidate_idx in enumerate(candidates_to_test):
candidate_idx_item = candidate_idx.item()
coordinates = mask_idx_to_coordinates(candidate_idx_item, mask_vision_model)
mask = generate_mask_from_coordinates(image, coordinates)
masked_image = apply_mask_with_mean(image, mask)
with torch.no_grad():
masked_image_inputs = processor(images=masked_image, return_tensors="pt").to(DEVICE)
masked_image_features = model.get_image_features(**masked_image_inputs)
masked_image_features = masked_image_features / masked_image_features.norm(dim=-1, keepdim=True)
masked_similarities = torch.matmul(masked_image_features, text_features.T)
masked_prediction = torch.argmax(masked_similarities, dim=-1).item()
masked_confidence = masked_similarities[0, predicted_class_idx].item()
if masked_prediction == predicted_class_idx:
return candidate_idx_item
calibration_results.append((candidate_idx_item, masked_confidence))
if len(sorted_candidates) > TOP_K:
return sorted_candidates[TOP_K].item()
else:
if calibration_results:
return max(calibration_results, key=lambda x: x[1])[0]
else:
return sorted_candidates[0].item()
def mask_idx_to_coordinates(mask_idx, mask_vision_model):
"""Convert mask token index to image coordinates using the pre-computed regions"""
if mask_idx >= len(mask_vision_model.regions):
raise ValueError(f"mask_idx {mask_idx} is out of range. Only {len(mask_vision_model.regions)} regions available.")
top, left, bottom, right = mask_vision_model.regions[mask_idx]
return ((top, left), (bottom, right))
def generate_mask_from_coordinates(image, coordinates):
"""Generate a binary mask from crop coordinates"""
H, W = 224, 224
mask = np.zeros((H, W), dtype=np.int8)
(top, left), (bottom, right) = coordinates
mask[top:bottom, left:right] = 1
return mask
print("Creating MaskCLIP model...")
mask_vision_model = MaskCLIPVisionTransformer(model.vision_model.config, retain_ratio=RETAIN_RATIO)
mask_vision_model.load_state_dict(model.vision_model.state_dict(), strict=False)
mask_vision_model = mask_vision_model.to(DEVICE)
mask_vision_model.eval()
print("MaskCLIP model created successfully.")
train_dataset = load_dataset("IOAI-official/IOAI-2025-Pixel-train", split="train")
class_names_eval = list(set([item['name'] for item in train_dataset])) + [BACKGROUND_CLASS]
print("Preparing text features...")
text_inputs_eval = processor(text=class_names_eval, return_tensors="pt", padding=True).to(DEVICE)
with torch.no_grad():
text_features_eval = model.get_text_features(**text_inputs_eval)
text_features_eval = text_features_eval / text_features_eval.norm(dim=-1, keepdim=True)
print("Text features prepared.")
masks = {}
total_correct = 0
total_processed = 0
for item in tqdm(dataset):
image = item['image']
total_processed += 1
try:
vision_outputs, full_image_features, mask_tokens_features = compute_vision_features_once(
model, image, mask_vision_model
)
full_image_similarities = torch.matmul(full_image_features, text_features_eval.T)
predicted_class_idx = torch.argmax(full_image_similarities, dim=-1).item()
best_mask_idx = find_best_mask_region_calibrated(
model, image, class_names_eval, mask_vision_model, text_features_eval,
vision_outputs=vision_outputs, full_image_features=full_image_features,
mask_tokens_features=mask_tokens_features
)
coordinates = mask_idx_to_coordinates(best_mask_idx, mask_vision_model)
mask = generate_mask_from_coordinates(image, coordinates)
assert mask.shape == (224, 224), "Mask should be 224x224"
assert mask.sum() <= RETAIN_RATIO * 224 * 224, "You should leave only 6.25% of pixels"
idx = item['idx']
masks[idx] = coordinates
except Exception as e:
print(f"Error processing image {item['idx']}: {e}")
if len(mask_vision_model.regions) > 0:
region_sizes = [(r[2]-r[0])*(r[3]-r[1]) for r in mask_vision_model.regions]
min_region_idx = region_sizes.index(min(region_sizes))
fallback_coords = mask_idx_to_coordinates(min_region_idx, mask_vision_model)
else:
fallback_coords = ((84, 84), (140, 140))
masks[item['idx']] = fallback_coords
with open('submission.jsonl', 'w') as f:
for idx, coordinates in masks.items():
json.dump({"idx": idx, "coordinates": coordinates}, f)
f.write('\n')
print("Masks saved to masks.jsonl")