Image Segmentation
Transformers
Safetensors
fc_clip
panoptic-segmentation
open-vocabulary
zero-shot
clip
convnext
mask2former
fiftyone
custom_code
Instructions to use Voxel51/fc-clip with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use Voxel51/fc-clip with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("image-segmentation", model="Voxel51/fc-clip", trust_remote_code=True)# Load model directly from transformers import AutoModel model = AutoModel.from_pretrained("Voxel51/fc-clip", trust_remote_code=True, device_map="auto") - Notebooks
- Google Colab
- Kaggle
Upload folder using huggingface_hub
Browse files- README.md +81 -0
- config.json +35 -0
- configuration_fc_clip.py +113 -0
- convert_and_upload.py +81 -0
- model.safetensors +3 -0
- modeling_fc_clip.py +1015 -0
- preprocessor_config.json +10 -0
README.md
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
license: mit
|
| 3 |
+
tags:
|
| 4 |
+
- panoptic-segmentation
|
| 5 |
+
- open-vocabulary
|
| 6 |
+
- zero-shot
|
| 7 |
+
- clip
|
| 8 |
+
- convnext
|
| 9 |
+
- mask2former
|
| 10 |
+
- fiftyone
|
| 11 |
+
library_name: transformers
|
| 12 |
+
pipeline_tag: image-segmentation
|
| 13 |
+
---
|
| 14 |
+
|
| 15 |
+
# FC-CLIP — Open-Vocabulary Panoptic Segmentation
|
| 16 |
+
|
| 17 |
+
FC-CLIP is an open-vocabulary panoptic segmentation model that pairs a frozen
|
| 18 |
+
**ConvNeXt-Large CLIP** backbone with a lightweight **Mask2Former** decoder.
|
| 19 |
+
It achieves strong zero-shot performance without requiring separate specialist
|
| 20 |
+
models for things vs. stuff.
|
| 21 |
+
|
| 22 |
+
This repository hosts the COCO Panoptic checkpoint uploaded to HuggingFace Hub
|
| 23 |
+
by Claude, for testing use with the **FiftyOne Model Zoo**.
|
| 24 |
+
|
| 25 |
+
## Attribution
|
| 26 |
+
|
| 27 |
+
> **Paper:** "A Simple Framework for Open-Vocabulary Segmentation and Detection"
|
| 28 |
+
> Jiarui Xu, Shalini De Mello, Sifei Liu, Wonmin Byeon, Thomas Breuel, Jan Kautz, Xiaolong Wang.
|
| 29 |
+
> CVPR 2023 · [arxiv 2311.15539](https://arxiv.org/abs/2311.15539)
|
| 30 |
+
|
| 31 |
+
> **Original code:** [bytedance/fc-clip](https://github.com/bytedance/fc-clip) — MIT License
|
| 32 |
+
|
| 33 |
+
## Usage
|
| 34 |
+
|
| 35 |
+
### Standalone (trust_remote_code)
|
| 36 |
+
|
| 37 |
+
```python
|
| 38 |
+
import torch
|
| 39 |
+
from transformers import AutoModel
|
| 40 |
+
|
| 41 |
+
model = AutoModel.from_pretrained("neerajaabhyankar/fc-clip", trust_remote_code=True)
|
| 42 |
+
model.eval()
|
| 43 |
+
|
| 44 |
+
# Preprocess: RGB uint8 numpy/PIL → normalised tensor
|
| 45 |
+
pixel_values = model.preprocess_image(your_pil_image) # [1, 3, H, W]
|
| 46 |
+
|
| 47 |
+
with torch.no_grad():
|
| 48 |
+
results = model(pixel_values)
|
| 49 |
+
|
| 50 |
+
panoptic_seg, segments_info = results[0]
|
| 51 |
+
# panoptic_seg: int32 tensor [H, W] — pixel → segment id
|
| 52 |
+
# segments_info: list[{"id", "category_id", "isthing"}]
|
| 53 |
+
```
|
| 54 |
+
|
| 55 |
+
### Open-vocabulary (custom classes)
|
| 56 |
+
|
| 57 |
+
```python
|
| 58 |
+
results = model(pixel_values, class_names=["cat", "dog", "sky", "grass"])
|
| 59 |
+
```
|
| 60 |
+
|
| 61 |
+
## Architecture
|
| 62 |
+
|
| 63 |
+
| Component | Detail |
|
| 64 |
+
|-----------|--------|
|
| 65 |
+
| Backbone | OpenCLIP ConvNeXt-Large (`convnext_large_d_320`, `laion2b_s29b_b131k_ft_soup`), **frozen** |
|
| 66 |
+
| Pixel decoder | 6-layer Multi-Scale Deformable Attention encoder + 1-level FPN |
|
| 67 |
+
| Transformer decoder | 5-layer Mask2Former cross-attention decoder, 250 queries |
|
| 68 |
+
| Text classification | VILD 14-template ensemble + geometric in-vocab/out-vocab blending |
|
| 69 |
+
| Classes | 133 COCO panoptic (80 things + 53 stuff) |
|
| 70 |
+
|
| 71 |
+
## Requirements
|
| 72 |
+
|
| 73 |
+
```
|
| 74 |
+
torch torchvision transformers open_clip_torch safetensors
|
| 75 |
+
```
|
| 76 |
+
|
| 77 |
+
No `detectron2` required — the model is self-contained.
|
| 78 |
+
|
| 79 |
+
## License
|
| 80 |
+
|
| 81 |
+
MIT (same as original bytedance/fc-clip)
|
config.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"model_type": "fc_clip",
|
| 3 |
+
"auto_map": {
|
| 4 |
+
"AutoConfig": "configuration_fc_clip.FCCLIPConfig",
|
| 5 |
+
"AutoModel": "modeling_fc_clip.FCCLIPForPanopticSegmentation"
|
| 6 |
+
},
|
| 7 |
+
"clip_model_name": "convnext_large_d_320",
|
| 8 |
+
"clip_pretrained": "laion2b_s29b_b131k_ft_soup",
|
| 9 |
+
"clip_embedding_dim": 768,
|
| 10 |
+
"conv_dim": 256,
|
| 11 |
+
"mask_dim": 256,
|
| 12 |
+
"transformer_dropout": 0.0,
|
| 13 |
+
"transformer_nheads": 8,
|
| 14 |
+
"transformer_dim_feedforward": 1024,
|
| 15 |
+
"transformer_enc_layers": 6,
|
| 16 |
+
"transformer_in_features": ["res3", "res4", "res5"],
|
| 17 |
+
"in_features": ["res2", "res3", "res4", "res5"],
|
| 18 |
+
"common_stride": 4,
|
| 19 |
+
"hidden_dim": 256,
|
| 20 |
+
"num_queries": 250,
|
| 21 |
+
"nheads": 8,
|
| 22 |
+
"dim_feedforward": 2048,
|
| 23 |
+
"dec_layers": 9,
|
| 24 |
+
"pre_norm": false,
|
| 25 |
+
"enforce_input_project": false,
|
| 26 |
+
"num_classes": 133,
|
| 27 |
+
"num_thing_classes": 80,
|
| 28 |
+
"geometric_ensemble_alpha": 0.4,
|
| 29 |
+
"geometric_ensemble_beta": 0.8,
|
| 30 |
+
"ensemble_on_valid_mask": false,
|
| 31 |
+
"object_mask_threshold": 0.8,
|
| 32 |
+
"overlap_threshold": 0.8,
|
| 33 |
+
"pixel_mean": [122.7709383, 116.7460125, 104.09373615],
|
| 34 |
+
"pixel_std": [68.5005327, 66.6321579, 70.32316305]
|
| 35 |
+
}
|
configuration_fc_clip.py
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""FCCLIPConfig — HuggingFace PretrainedConfig for FC-CLIP."""
|
| 2 |
+
|
| 3 |
+
from transformers import PretrainedConfig
|
| 4 |
+
|
| 5 |
+
# COCO panoptic 133 classes: things (0-79) then stuff (80-132)
|
| 6 |
+
COCO_PANOPTIC_CLASSES = [
|
| 7 |
+
# 80 thing classes
|
| 8 |
+
"person", "bicycle", "car", "motorcycle", "airplane", "bus", "train",
|
| 9 |
+
"truck", "boat", "traffic light", "fire hydrant", "stop sign",
|
| 10 |
+
"parking meter", "bench", "bird", "cat", "dog", "horse", "sheep", "cow",
|
| 11 |
+
"elephant", "bear", "zebra", "giraffe", "backpack", "umbrella", "handbag",
|
| 12 |
+
"tie", "suitcase", "frisbee", "skis", "snowboard", "sports ball", "kite",
|
| 13 |
+
"baseball bat", "baseball glove", "skateboard", "surfboard",
|
| 14 |
+
"tennis racket", "bottle", "wine glass", "cup", "fork", "knife", "spoon",
|
| 15 |
+
"bowl", "banana", "apple", "sandwich", "orange", "broccoli", "carrot",
|
| 16 |
+
"hot dog", "pizza", "donut", "cake", "chair", "couch", "potted plant",
|
| 17 |
+
"bed", "dining table", "toilet", "tv", "laptop", "mouse", "remote",
|
| 18 |
+
"keyboard", "cell phone", "microwave", "oven", "toaster", "sink",
|
| 19 |
+
"refrigerator", "book", "clock", "vase", "scissors", "teddy bear",
|
| 20 |
+
"hair drier", "toothbrush",
|
| 21 |
+
# 53 stuff classes
|
| 22 |
+
"banner", "blanket", "bridge", "cardboard", "counter", "curtain",
|
| 23 |
+
"door-stuff", "floor-wood", "flower", "fruit", "gravel", "house",
|
| 24 |
+
"light", "mirror-stuff", "net", "pillow", "platform", "playingfield",
|
| 25 |
+
"railroad", "river", "road", "roof", "sand", "sea", "shelf", "snow",
|
| 26 |
+
"stairs", "tent", "towel", "wall-brick", "wall-stone", "wall-tile",
|
| 27 |
+
"wall-wood", "water-other", "window-blind", "window-other",
|
| 28 |
+
"tree-merged", "fence-merged", "ceiling-merged", "sky-other-merged",
|
| 29 |
+
"cabinet-merged", "table-merged", "floor-other-merged",
|
| 30 |
+
"pavement-merged", "mountain-merged", "grass-merged", "dirt-merged",
|
| 31 |
+
"paper-merged", "food-other-merged", "building-other-merged",
|
| 32 |
+
"rock-merged", "wall-other-merged", "rug-merged",
|
| 33 |
+
]
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
class FCCLIPConfig(PretrainedConfig):
|
| 37 |
+
model_type = "fc_clip"
|
| 38 |
+
|
| 39 |
+
def __init__(
|
| 40 |
+
self,
|
| 41 |
+
# CLIP backbone
|
| 42 |
+
clip_model_name="convnext_large_d_320",
|
| 43 |
+
clip_pretrained="laion2b_s29b_b131k_ft_soup",
|
| 44 |
+
clip_embedding_dim=768,
|
| 45 |
+
# pixel decoder
|
| 46 |
+
conv_dim=256,
|
| 47 |
+
mask_dim=256,
|
| 48 |
+
transformer_dropout=0.0,
|
| 49 |
+
transformer_nheads=8,
|
| 50 |
+
transformer_dim_feedforward=1024,
|
| 51 |
+
transformer_enc_layers=6,
|
| 52 |
+
transformer_in_features=("res3", "res4", "res5"),
|
| 53 |
+
in_features=("res2", "res3", "res4", "res5"),
|
| 54 |
+
common_stride=4,
|
| 55 |
+
# transformer decoder
|
| 56 |
+
hidden_dim=256,
|
| 57 |
+
num_queries=250,
|
| 58 |
+
nheads=8,
|
| 59 |
+
dim_feedforward=2048,
|
| 60 |
+
dec_layers=9,
|
| 61 |
+
pre_norm=False,
|
| 62 |
+
enforce_input_project=False,
|
| 63 |
+
# classification
|
| 64 |
+
num_classes=133,
|
| 65 |
+
num_thing_classes=80,
|
| 66 |
+
stuff_classes=None,
|
| 67 |
+
# FC-CLIP ensembling
|
| 68 |
+
geometric_ensemble_alpha=0.4,
|
| 69 |
+
geometric_ensemble_beta=0.8,
|
| 70 |
+
ensemble_on_valid_mask=False,
|
| 71 |
+
# inference thresholds
|
| 72 |
+
object_mask_threshold=0.8,
|
| 73 |
+
overlap_threshold=0.8,
|
| 74 |
+
# image normalisation (CLIP values, 0-255 scale, RGB)
|
| 75 |
+
pixel_mean=(122.7709383, 116.7460125, 104.09373615),
|
| 76 |
+
pixel_std=(68.5005327, 66.6321579, 70.32316305),
|
| 77 |
+
**kwargs,
|
| 78 |
+
):
|
| 79 |
+
super().__init__(**kwargs)
|
| 80 |
+
self.clip_model_name = clip_model_name
|
| 81 |
+
self.clip_pretrained = clip_pretrained
|
| 82 |
+
self.clip_embedding_dim = clip_embedding_dim
|
| 83 |
+
|
| 84 |
+
self.conv_dim = conv_dim
|
| 85 |
+
self.mask_dim = mask_dim
|
| 86 |
+
self.transformer_dropout = transformer_dropout
|
| 87 |
+
self.transformer_nheads = transformer_nheads
|
| 88 |
+
self.transformer_dim_feedforward = transformer_dim_feedforward
|
| 89 |
+
self.transformer_enc_layers = transformer_enc_layers
|
| 90 |
+
self.transformer_in_features = list(transformer_in_features)
|
| 91 |
+
self.in_features = list(in_features)
|
| 92 |
+
self.common_stride = common_stride
|
| 93 |
+
|
| 94 |
+
self.hidden_dim = hidden_dim
|
| 95 |
+
self.num_queries = num_queries
|
| 96 |
+
self.nheads = nheads
|
| 97 |
+
self.dim_feedforward = dim_feedforward
|
| 98 |
+
self.dec_layers = dec_layers
|
| 99 |
+
self.pre_norm = pre_norm
|
| 100 |
+
self.enforce_input_project = enforce_input_project
|
| 101 |
+
|
| 102 |
+
self.num_classes = num_classes
|
| 103 |
+
self.num_thing_classes = num_thing_classes
|
| 104 |
+
self.stuff_classes = stuff_classes if stuff_classes is not None else COCO_PANOPTIC_CLASSES
|
| 105 |
+
|
| 106 |
+
self.geometric_ensemble_alpha = geometric_ensemble_alpha
|
| 107 |
+
self.geometric_ensemble_beta = geometric_ensemble_beta
|
| 108 |
+
self.ensemble_on_valid_mask = ensemble_on_valid_mask
|
| 109 |
+
self.object_mask_threshold = object_mask_threshold
|
| 110 |
+
self.overlap_threshold = overlap_threshold
|
| 111 |
+
|
| 112 |
+
self.pixel_mean = list(pixel_mean)
|
| 113 |
+
self.pixel_std = list(pixel_std)
|
convert_and_upload.py
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Convert FC-CLIP checkpoint and upload to HuggingFace Hub.
|
| 3 |
+
|
| 4 |
+
Run AFTER logging in:
|
| 5 |
+
huggingface-cli login
|
| 6 |
+
|
| 7 |
+
Then:
|
| 8 |
+
python convert_and_upload.py
|
| 9 |
+
"""
|
| 10 |
+
|
| 11 |
+
import os
|
| 12 |
+
import sys
|
| 13 |
+
|
| 14 |
+
CHECKPOINT_PATH = os.path.expanduser(
|
| 15 |
+
"~/fiftyone/__models__/fcclip/models/fcclip_cocopan.pth"
|
| 16 |
+
)
|
| 17 |
+
HF_REPO_DIR = os.path.dirname(os.path.abspath(__file__))
|
| 18 |
+
HF_REPO_ID = "neerajaabhyankar/fc-clip"
|
| 19 |
+
|
| 20 |
+
# ---------------------------------------------------------------------------
|
| 21 |
+
# Step 1 — Convert .pth → model.safetensors
|
| 22 |
+
# ---------------------------------------------------------------------------
|
| 23 |
+
|
| 24 |
+
def convert():
|
| 25 |
+
import torch
|
| 26 |
+
from safetensors.torch import save_file
|
| 27 |
+
|
| 28 |
+
print(f"Loading checkpoint from {CHECKPOINT_PATH} ...")
|
| 29 |
+
ckpt = torch.load(CHECKPOINT_PATH, map_location="cpu", weights_only=False)
|
| 30 |
+
state_dict = ckpt.get("model", ckpt)
|
| 31 |
+
|
| 32 |
+
# Drop training-only keys (criterion.*) and backbone CLIP weights
|
| 33 |
+
# (backbone loads from open_clip at runtime; no need to store in safetensors)
|
| 34 |
+
skip_prefixes = ("criterion.", "backbone.")
|
| 35 |
+
filtered = {k: v for k, v in state_dict.items()
|
| 36 |
+
if not any(k.startswith(p) for p in skip_prefixes)}
|
| 37 |
+
|
| 38 |
+
print(f"Keeping {len(filtered)}/{len(state_dict)} keys (dropped backbone + criterion)")
|
| 39 |
+
for k, v in sorted(filtered.items()):
|
| 40 |
+
print(f" {k:80s} {tuple(v.shape)}")
|
| 41 |
+
|
| 42 |
+
out_path = os.path.join(HF_REPO_DIR, "model.safetensors")
|
| 43 |
+
save_file(filtered, out_path)
|
| 44 |
+
size_mb = os.path.getsize(out_path) / 1e6
|
| 45 |
+
print(f"\nSaved {out_path} ({size_mb:.1f} MB)")
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
# ---------------------------------------------------------------------------
|
| 49 |
+
# Step 2 — Upload folder to HF Hub
|
| 50 |
+
# ---------------------------------------------------------------------------
|
| 51 |
+
|
| 52 |
+
def upload():
|
| 53 |
+
from huggingface_hub import HfApi
|
| 54 |
+
|
| 55 |
+
api = HfApi()
|
| 56 |
+
|
| 57 |
+
print(f"\nCreating/verifying repo {HF_REPO_ID} ...")
|
| 58 |
+
try:
|
| 59 |
+
api.create_repo(HF_REPO_ID, repo_type="model", private=False, exist_ok=True)
|
| 60 |
+
print(" Repo ready.")
|
| 61 |
+
except Exception as e:
|
| 62 |
+
print(f" create_repo: {e} (may already exist, continuing)")
|
| 63 |
+
|
| 64 |
+
print(f"Uploading {HF_REPO_DIR} → {HF_REPO_ID} ...")
|
| 65 |
+
api.upload_folder(
|
| 66 |
+
folder_path=HF_REPO_DIR,
|
| 67 |
+
repo_id=HF_REPO_ID,
|
| 68 |
+
repo_type="model",
|
| 69 |
+
# Skip any local-only scripts/large intermediates
|
| 70 |
+
ignore_patterns=["*.pth", "__pycache__", "*.pyc", ".DS_Store"],
|
| 71 |
+
)
|
| 72 |
+
print(f"\nDone! https://huggingface.co/{HF_REPO_ID}")
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
if __name__ == "__main__":
|
| 76 |
+
safetensors_path = os.path.join(HF_REPO_DIR, "model.safetensors")
|
| 77 |
+
if not os.path.exists(safetensors_path):
|
| 78 |
+
convert()
|
| 79 |
+
else:
|
| 80 |
+
print(f"model.safetensors already exists at {safetensors_path}, skipping conversion.")
|
| 81 |
+
upload()
|
model.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:568d231866dde03197bd674a986d6a2bf1970306ea21d03567577e0ed4088b83
|
| 3 |
+
size 82928612
|
modeling_fc_clip.py
ADDED
|
@@ -0,0 +1,1015 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""FC-CLIP: self-contained HuggingFace model (no detectron2).
|
| 2 |
+
|
| 3 |
+
Original paper: "A Simple Framework for Open-Vocabulary Segmentation and
|
| 4 |
+
Detection" (Yu et al., CVPR 2023). Original code: bytedance/fc-clip (MIT).
|
| 5 |
+
|
| 6 |
+
Architecture:
|
| 7 |
+
backbone — frozen OpenCLIP ConvNeXt-Large
|
| 8 |
+
pixel_decoder — 6-layer MSDA encoder + FPN (pure-Python MSDA fallback)
|
| 9 |
+
predictor — 5-layer Mask2Former cross-attention decoder
|
| 10 |
+
void_embedding — learnable background class embedding
|
| 11 |
+
"""
|
| 12 |
+
|
| 13 |
+
import copy
|
| 14 |
+
import math
|
| 15 |
+
import warnings
|
| 16 |
+
|
| 17 |
+
import numpy as np
|
| 18 |
+
import torch
|
| 19 |
+
import torch.nn.functional as F
|
| 20 |
+
from torch import nn, Tensor
|
| 21 |
+
from torch.nn.init import xavier_uniform_, constant_, normal_
|
| 22 |
+
|
| 23 |
+
from transformers import PreTrainedModel
|
| 24 |
+
from .configuration_fc_clip import FCCLIPConfig
|
| 25 |
+
|
| 26 |
+
# ---------------------------------------------------------------------------
|
| 27 |
+
# VILD prompt templates (for open-vocabulary text classification)
|
| 28 |
+
# ---------------------------------------------------------------------------
|
| 29 |
+
|
| 30 |
+
VILD_PROMPT = [
|
| 31 |
+
"a photo of a {}.",
|
| 32 |
+
"This is a photo of a {}",
|
| 33 |
+
"There is a {} in the scene",
|
| 34 |
+
"There is the {} in the scene",
|
| 35 |
+
"a photo of a {} in the scene",
|
| 36 |
+
"a photo of a small {}.",
|
| 37 |
+
"a photo of a medium {}.",
|
| 38 |
+
"a photo of a large {}.",
|
| 39 |
+
"This is a photo of a small {}.",
|
| 40 |
+
"This is a photo of a medium {}.",
|
| 41 |
+
"This is a photo of a large {}.",
|
| 42 |
+
"There is a small {} in the scene.",
|
| 43 |
+
"There is a medium {} in the scene.",
|
| 44 |
+
"There is a large {} in the scene.",
|
| 45 |
+
]
|
| 46 |
+
|
| 47 |
+
# ---------------------------------------------------------------------------
|
| 48 |
+
# Pure-Python Multi-Scale Deformable Attention
|
| 49 |
+
# (same maths as the CUDA op; CPU/GPU portable)
|
| 50 |
+
# ---------------------------------------------------------------------------
|
| 51 |
+
|
| 52 |
+
def _ms_deform_attn_core(value, value_spatial_shapes, sampling_locations, attention_weights):
|
| 53 |
+
N_, S_, M_, D_ = value.shape
|
| 54 |
+
_, Lq_, M_, L_, P_, _ = sampling_locations.shape
|
| 55 |
+
value_list = value.split([H_ * W_ for H_, W_ in value_spatial_shapes], dim=1)
|
| 56 |
+
sampling_grids = 2 * sampling_locations - 1
|
| 57 |
+
sampling_value_list = []
|
| 58 |
+
for lid_, (H_, W_) in enumerate(value_spatial_shapes):
|
| 59 |
+
value_l_ = value_list[lid_].flatten(2).transpose(1, 2).reshape(N_ * M_, D_, H_, W_)
|
| 60 |
+
sampling_grid_l_ = sampling_grids[:, :, :, lid_].transpose(1, 2).flatten(0, 1)
|
| 61 |
+
sampling_value_l_ = F.grid_sample(
|
| 62 |
+
value_l_, sampling_grid_l_, mode="bilinear", padding_mode="zeros", align_corners=False
|
| 63 |
+
)
|
| 64 |
+
sampling_value_list.append(sampling_value_l_)
|
| 65 |
+
attention_weights = attention_weights.transpose(1, 2).reshape(N_ * M_, 1, Lq_, L_ * P_)
|
| 66 |
+
output = (torch.stack(sampling_value_list, dim=-2).flatten(-2) * attention_weights).sum(-1).view(
|
| 67 |
+
N_, M_ * D_, Lq_
|
| 68 |
+
)
|
| 69 |
+
return output.transpose(1, 2).contiguous()
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
def _is_power_of_2(n):
|
| 73 |
+
return isinstance(n, int) and n > 0 and (n & (n - 1)) == 0
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
class MSDeformAttn(nn.Module):
|
| 77 |
+
def __init__(self, d_model=256, n_levels=4, n_heads=8, n_points=4):
|
| 78 |
+
super().__init__()
|
| 79 |
+
if d_model % n_heads != 0:
|
| 80 |
+
raise ValueError(f"d_model ({d_model}) must be divisible by n_heads ({n_heads})")
|
| 81 |
+
if not _is_power_of_2(d_model // n_heads):
|
| 82 |
+
warnings.warn("d_model // n_heads is not a power of 2; CUDA impl would be suboptimal")
|
| 83 |
+
self.d_model = d_model
|
| 84 |
+
self.n_levels = n_levels
|
| 85 |
+
self.n_heads = n_heads
|
| 86 |
+
self.n_points = n_points
|
| 87 |
+
self.sampling_offsets = nn.Linear(d_model, n_heads * n_levels * n_points * 2)
|
| 88 |
+
self.attention_weights = nn.Linear(d_model, n_heads * n_levels * n_points)
|
| 89 |
+
self.value_proj = nn.Linear(d_model, d_model)
|
| 90 |
+
self.output_proj = nn.Linear(d_model, d_model)
|
| 91 |
+
self._reset_parameters()
|
| 92 |
+
|
| 93 |
+
def _reset_parameters(self):
|
| 94 |
+
constant_(self.sampling_offsets.weight, 0.0)
|
| 95 |
+
thetas = torch.arange(self.n_heads, dtype=torch.float32) * (2.0 * math.pi / self.n_heads)
|
| 96 |
+
grid_init = torch.stack([thetas.cos(), thetas.sin()], -1)
|
| 97 |
+
grid_init = (grid_init / grid_init.abs().max(-1, keepdim=True)[0]).view(
|
| 98 |
+
self.n_heads, 1, 1, 2
|
| 99 |
+
).repeat(1, self.n_levels, self.n_points, 1)
|
| 100 |
+
for i in range(self.n_points):
|
| 101 |
+
grid_init[:, :, i, :] *= i + 1
|
| 102 |
+
with torch.no_grad():
|
| 103 |
+
self.sampling_offsets.bias = nn.Parameter(grid_init.view(-1))
|
| 104 |
+
constant_(self.attention_weights.weight, 0.0)
|
| 105 |
+
constant_(self.attention_weights.bias, 0.0)
|
| 106 |
+
xavier_uniform_(self.value_proj.weight)
|
| 107 |
+
constant_(self.value_proj.bias, 0.0)
|
| 108 |
+
xavier_uniform_(self.output_proj.weight)
|
| 109 |
+
constant_(self.output_proj.bias, 0.0)
|
| 110 |
+
|
| 111 |
+
def forward(self, query, reference_points, input_flatten, input_spatial_shapes,
|
| 112 |
+
input_level_start_index, input_padding_mask=None):
|
| 113 |
+
N, Len_q, _ = query.shape
|
| 114 |
+
N, Len_in, _ = input_flatten.shape
|
| 115 |
+
|
| 116 |
+
value = self.value_proj(input_flatten)
|
| 117 |
+
if input_padding_mask is not None:
|
| 118 |
+
value = value.masked_fill(input_padding_mask[..., None], 0.0)
|
| 119 |
+
value = value.view(N, Len_in, self.n_heads, self.d_model // self.n_heads)
|
| 120 |
+
|
| 121 |
+
sampling_offsets = self.sampling_offsets(query).view(N, Len_q, self.n_heads, self.n_levels, self.n_points, 2)
|
| 122 |
+
attention_weights = self.attention_weights(query).view(N, Len_q, self.n_heads, self.n_levels * self.n_points)
|
| 123 |
+
attention_weights = F.softmax(attention_weights, -1).view(N, Len_q, self.n_heads, self.n_levels, self.n_points)
|
| 124 |
+
|
| 125 |
+
if reference_points.shape[-1] == 2:
|
| 126 |
+
offset_normalizer = torch.stack(
|
| 127 |
+
[input_spatial_shapes[..., 1], input_spatial_shapes[..., 0]], -1
|
| 128 |
+
)
|
| 129 |
+
sampling_locations = (
|
| 130 |
+
reference_points[:, :, None, :, None, :]
|
| 131 |
+
+ sampling_offsets / offset_normalizer[None, None, None, :, None, :]
|
| 132 |
+
)
|
| 133 |
+
elif reference_points.shape[-1] == 4:
|
| 134 |
+
sampling_locations = (
|
| 135 |
+
reference_points[:, :, None, :, None, :2]
|
| 136 |
+
+ sampling_offsets / self.n_points
|
| 137 |
+
* reference_points[:, :, None, :, None, 2:]
|
| 138 |
+
* 0.5
|
| 139 |
+
)
|
| 140 |
+
else:
|
| 141 |
+
raise ValueError(f"Last dim of reference_points must be 2 or 4, got {reference_points.shape[-1]}")
|
| 142 |
+
|
| 143 |
+
output = _ms_deform_attn_core(value, input_spatial_shapes, sampling_locations, attention_weights)
|
| 144 |
+
output = self.output_proj(output)
|
| 145 |
+
return output
|
| 146 |
+
|
| 147 |
+
|
| 148 |
+
# ---------------------------------------------------------------------------
|
| 149 |
+
# Positional encoding
|
| 150 |
+
# ---------------------------------------------------------------------------
|
| 151 |
+
|
| 152 |
+
class PositionEmbeddingSine(nn.Module):
|
| 153 |
+
def __init__(self, num_pos_feats=64, temperature=10000, normalize=False, scale=None):
|
| 154 |
+
super().__init__()
|
| 155 |
+
self.num_pos_feats = num_pos_feats
|
| 156 |
+
self.temperature = temperature
|
| 157 |
+
self.normalize = normalize
|
| 158 |
+
self.scale = 2 * math.pi if scale is None else scale
|
| 159 |
+
|
| 160 |
+
def forward(self, x, mask=None):
|
| 161 |
+
if mask is None:
|
| 162 |
+
mask = torch.zeros((x.size(0), x.size(2), x.size(3)), device=x.device, dtype=torch.bool)
|
| 163 |
+
not_mask = ~mask
|
| 164 |
+
y_embed = not_mask.cumsum(1, dtype=torch.float32)
|
| 165 |
+
x_embed = not_mask.cumsum(2, dtype=torch.float32)
|
| 166 |
+
if self.normalize:
|
| 167 |
+
eps = 1e-6
|
| 168 |
+
y_embed = y_embed / (y_embed[:, -1:, :] + eps) * self.scale
|
| 169 |
+
x_embed = x_embed / (x_embed[:, :, -1:] + eps) * self.scale
|
| 170 |
+
dim_t = torch.arange(self.num_pos_feats, dtype=torch.float32, device=x.device)
|
| 171 |
+
dim_t = self.temperature ** (2 * (dim_t // 2) / self.num_pos_feats)
|
| 172 |
+
pos_x = x_embed[:, :, :, None] / dim_t
|
| 173 |
+
pos_y = y_embed[:, :, :, None] / dim_t
|
| 174 |
+
pos_x = torch.stack((pos_x[:, :, :, 0::2].sin(), pos_x[:, :, :, 1::2].cos()), dim=4).flatten(3)
|
| 175 |
+
pos_y = torch.stack((pos_y[:, :, :, 0::2].sin(), pos_y[:, :, :, 1::2].cos()), dim=4).flatten(3)
|
| 176 |
+
return torch.cat((pos_y, pos_x), dim=3).permute(0, 3, 1, 2)
|
| 177 |
+
|
| 178 |
+
|
| 179 |
+
# ---------------------------------------------------------------------------
|
| 180 |
+
# Pixel decoder helpers
|
| 181 |
+
# ---------------------------------------------------------------------------
|
| 182 |
+
|
| 183 |
+
def _get_clones(module, N):
|
| 184 |
+
return nn.ModuleList([copy.deepcopy(module) for _ in range(N)])
|
| 185 |
+
|
| 186 |
+
|
| 187 |
+
def _get_activation_fn(activation):
|
| 188 |
+
if activation == "relu":
|
| 189 |
+
return F.relu
|
| 190 |
+
if activation == "gelu":
|
| 191 |
+
return F.gelu
|
| 192 |
+
raise RuntimeError(f"activation must be relu/gelu, not {activation}")
|
| 193 |
+
|
| 194 |
+
|
| 195 |
+
class Conv2dWithNorm(nn.Conv2d):
|
| 196 |
+
"""nn.Conv2d + GroupNorm with detectron2-compatible state_dict keys.
|
| 197 |
+
|
| 198 |
+
State dict: ``{name}.weight``, ``{name}.norm.weight``, ``{name}.norm.bias``
|
| 199 |
+
(no conv bias — use_bias=False matches the pixel decoder's GN config).
|
| 200 |
+
"""
|
| 201 |
+
|
| 202 |
+
def __init__(self, in_channels, out_channels, kernel_size, stride=1, padding=0, activation=None):
|
| 203 |
+
super().__init__(in_channels, out_channels, kernel_size, stride=stride, padding=padding, bias=False)
|
| 204 |
+
self.norm = nn.GroupNorm(32, out_channels)
|
| 205 |
+
self.activation = activation
|
| 206 |
+
|
| 207 |
+
def forward(self, x):
|
| 208 |
+
x = self.norm(super().forward(x))
|
| 209 |
+
if self.activation is not None:
|
| 210 |
+
x = self.activation(x)
|
| 211 |
+
return x
|
| 212 |
+
|
| 213 |
+
|
| 214 |
+
class MSDeformAttnTransformerEncoderLayer(nn.Module):
|
| 215 |
+
def __init__(self, d_model=256, d_ffn=1024, dropout=0.1, activation="relu",
|
| 216 |
+
n_levels=4, n_heads=8, n_points=4):
|
| 217 |
+
super().__init__()
|
| 218 |
+
self.self_attn = MSDeformAttn(d_model, n_levels, n_heads, n_points)
|
| 219 |
+
self.dropout1 = nn.Dropout(dropout)
|
| 220 |
+
self.norm1 = nn.LayerNorm(d_model)
|
| 221 |
+
self.linear1 = nn.Linear(d_model, d_ffn)
|
| 222 |
+
self.activation = _get_activation_fn(activation)
|
| 223 |
+
self.dropout2 = nn.Dropout(dropout)
|
| 224 |
+
self.linear2 = nn.Linear(d_ffn, d_model)
|
| 225 |
+
self.dropout3 = nn.Dropout(dropout)
|
| 226 |
+
self.norm2 = nn.LayerNorm(d_model)
|
| 227 |
+
|
| 228 |
+
@staticmethod
|
| 229 |
+
def with_pos_embed(tensor, pos):
|
| 230 |
+
return tensor if pos is None else tensor + pos
|
| 231 |
+
|
| 232 |
+
def forward_ffn(self, src):
|
| 233 |
+
src2 = self.linear2(self.dropout2(self.activation(self.linear1(src))))
|
| 234 |
+
src = src + self.dropout3(src2)
|
| 235 |
+
return self.norm2(src)
|
| 236 |
+
|
| 237 |
+
def forward(self, src, pos, reference_points, spatial_shapes, level_start_index, padding_mask=None):
|
| 238 |
+
src2 = self.self_attn(self.with_pos_embed(src, pos), reference_points, src,
|
| 239 |
+
spatial_shapes, level_start_index, padding_mask)
|
| 240 |
+
src = self.norm1(src + self.dropout1(src2))
|
| 241 |
+
return self.forward_ffn(src)
|
| 242 |
+
|
| 243 |
+
|
| 244 |
+
class MSDeformAttnTransformerEncoder(nn.Module):
|
| 245 |
+
def __init__(self, encoder_layer, num_layers):
|
| 246 |
+
super().__init__()
|
| 247 |
+
self.layers = _get_clones(encoder_layer, num_layers)
|
| 248 |
+
self.num_layers = num_layers
|
| 249 |
+
|
| 250 |
+
@staticmethod
|
| 251 |
+
def get_reference_points(spatial_shapes, valid_ratios, device):
|
| 252 |
+
reference_points_list = []
|
| 253 |
+
for lvl, (H_, W_) in enumerate(spatial_shapes):
|
| 254 |
+
ref_y, ref_x = torch.meshgrid(
|
| 255 |
+
torch.linspace(0.5, H_ - 0.5, H_, dtype=torch.float32, device=device),
|
| 256 |
+
torch.linspace(0.5, W_ - 0.5, W_, dtype=torch.float32, device=device),
|
| 257 |
+
indexing="ij",
|
| 258 |
+
)
|
| 259 |
+
ref_y = ref_y.reshape(-1)[None] / (valid_ratios[:, None, lvl, 1] * H_)
|
| 260 |
+
ref_x = ref_x.reshape(-1)[None] / (valid_ratios[:, None, lvl, 0] * W_)
|
| 261 |
+
reference_points_list.append(torch.stack((ref_x, ref_y), -1))
|
| 262 |
+
reference_points = torch.cat(reference_points_list, 1)
|
| 263 |
+
return reference_points[:, :, None] * valid_ratios[:, None]
|
| 264 |
+
|
| 265 |
+
def forward(self, src, spatial_shapes, level_start_index, valid_ratios, pos=None, padding_mask=None):
|
| 266 |
+
output = src
|
| 267 |
+
reference_points = self.get_reference_points(spatial_shapes, valid_ratios, device=src.device)
|
| 268 |
+
for layer in self.layers:
|
| 269 |
+
output = layer(output, pos, reference_points, spatial_shapes, level_start_index, padding_mask)
|
| 270 |
+
return output
|
| 271 |
+
|
| 272 |
+
|
| 273 |
+
class MSDeformAttnTransformerEncoderOnly(nn.Module):
|
| 274 |
+
def __init__(self, d_model=256, nhead=8, num_encoder_layers=6, dim_feedforward=1024,
|
| 275 |
+
dropout=0.1, activation="relu", num_feature_levels=4, enc_n_points=4):
|
| 276 |
+
super().__init__()
|
| 277 |
+
self.d_model = d_model
|
| 278 |
+
self.nhead = nhead
|
| 279 |
+
encoder_layer = MSDeformAttnTransformerEncoderLayer(
|
| 280 |
+
d_model, dim_feedforward, dropout, activation, num_feature_levels, nhead, enc_n_points
|
| 281 |
+
)
|
| 282 |
+
self.encoder = MSDeformAttnTransformerEncoder(encoder_layer, num_encoder_layers)
|
| 283 |
+
self.level_embed = nn.Parameter(torch.Tensor(num_feature_levels, d_model))
|
| 284 |
+
self._reset_parameters()
|
| 285 |
+
|
| 286 |
+
def _reset_parameters(self):
|
| 287 |
+
for p in self.parameters():
|
| 288 |
+
if p.dim() > 1:
|
| 289 |
+
nn.init.xavier_uniform_(p)
|
| 290 |
+
for m in self.modules():
|
| 291 |
+
if isinstance(m, MSDeformAttn):
|
| 292 |
+
m._reset_parameters()
|
| 293 |
+
normal_(self.level_embed)
|
| 294 |
+
|
| 295 |
+
def get_valid_ratio(self, mask):
|
| 296 |
+
_, H, W = mask.shape
|
| 297 |
+
valid_H = torch.sum(~mask[:, :, 0], 1)
|
| 298 |
+
valid_W = torch.sum(~mask[:, 0, :], 1)
|
| 299 |
+
return torch.stack([valid_W.float() / W, valid_H.float() / H], -1)
|
| 300 |
+
|
| 301 |
+
def forward(self, srcs, pos_embeds):
|
| 302 |
+
masks = [torch.zeros((x.size(0), x.size(2), x.size(3)), device=x.device, dtype=torch.bool) for x in srcs]
|
| 303 |
+
src_flatten, mask_flatten, lvl_pos_embed_flatten, spatial_shapes = [], [], [], []
|
| 304 |
+
for lvl, (src, mask, pos_embed) in enumerate(zip(srcs, masks, pos_embeds)):
|
| 305 |
+
bs, c, h, w = src.shape
|
| 306 |
+
spatial_shapes.append((h, w))
|
| 307 |
+
src = src.flatten(2).transpose(1, 2)
|
| 308 |
+
mask = mask.flatten(1)
|
| 309 |
+
pos_embed = pos_embed.flatten(2).transpose(1, 2)
|
| 310 |
+
lvl_pos_embed_flatten.append(pos_embed + self.level_embed[lvl].view(1, 1, -1))
|
| 311 |
+
src_flatten.append(src)
|
| 312 |
+
mask_flatten.append(mask)
|
| 313 |
+
src_flatten = torch.cat(src_flatten, 1)
|
| 314 |
+
mask_flatten = torch.cat(mask_flatten, 1)
|
| 315 |
+
lvl_pos_embed_flatten = torch.cat(lvl_pos_embed_flatten, 1)
|
| 316 |
+
spatial_shapes = torch.as_tensor(spatial_shapes, dtype=torch.long, device=src_flatten.device)
|
| 317 |
+
level_start_index = torch.cat((spatial_shapes.new_zeros((1,)), spatial_shapes.prod(1).cumsum(0)[:-1]))
|
| 318 |
+
valid_ratios = torch.stack([self.get_valid_ratio(m) for m in masks], 1)
|
| 319 |
+
memory = self.encoder(src_flatten, spatial_shapes, level_start_index, valid_ratios,
|
| 320 |
+
lvl_pos_embed_flatten, mask_flatten)
|
| 321 |
+
return memory, spatial_shapes, level_start_index
|
| 322 |
+
|
| 323 |
+
|
| 324 |
+
# ---------------------------------------------------------------------------
|
| 325 |
+
# Pixel Decoder
|
| 326 |
+
# ---------------------------------------------------------------------------
|
| 327 |
+
|
| 328 |
+
class MSDeformAttnPixelDecoder(nn.Module):
|
| 329 |
+
def __init__(self, config):
|
| 330 |
+
super().__init__()
|
| 331 |
+
# Feature channel sizes for ConvNeXt-Large: res2=192, res3=384, res4=768, res5=1536
|
| 332 |
+
feature_channels = {"res2": 192, "res3": 384, "res4": 768, "res5": 1536}
|
| 333 |
+
transformer_in_channels = [feature_channels[f] for f in config.transformer_in_features]
|
| 334 |
+
|
| 335 |
+
self.transformer_in_features = config.transformer_in_features # ["res3","res4","res5"]
|
| 336 |
+
self.transformer_num_feature_levels = len(self.transformer_in_features)
|
| 337 |
+
self.in_features = config.in_features # ["res2","res3","res4","res5"]
|
| 338 |
+
self.maskformer_num_feature_levels = 3
|
| 339 |
+
self.common_stride = config.common_stride
|
| 340 |
+
|
| 341 |
+
# min transformer stride = 8 (res3), num_fpn_levels = log2(8)-log2(4) = 1
|
| 342 |
+
self.num_fpn_levels = 1
|
| 343 |
+
conv_dim = config.conv_dim
|
| 344 |
+
mask_dim = config.mask_dim
|
| 345 |
+
|
| 346 |
+
# Input projections: res5 → res3 (reversed = high to low stride order)
|
| 347 |
+
input_proj_list = []
|
| 348 |
+
for in_ch in transformer_in_channels[::-1]:
|
| 349 |
+
input_proj_list.append(
|
| 350 |
+
nn.Sequential(
|
| 351 |
+
nn.Conv2d(in_ch, conv_dim, kernel_size=1),
|
| 352 |
+
nn.GroupNorm(32, conv_dim),
|
| 353 |
+
)
|
| 354 |
+
)
|
| 355 |
+
self.input_proj = nn.ModuleList(input_proj_list)
|
| 356 |
+
for proj in self.input_proj:
|
| 357 |
+
xavier_uniform_(proj[0].weight, gain=1)
|
| 358 |
+
constant_(proj[0].bias, 0)
|
| 359 |
+
|
| 360 |
+
self.transformer = MSDeformAttnTransformerEncoderOnly(
|
| 361 |
+
d_model=conv_dim,
|
| 362 |
+
dropout=config.transformer_dropout,
|
| 363 |
+
nhead=config.transformer_nheads,
|
| 364 |
+
dim_feedforward=config.transformer_dim_feedforward,
|
| 365 |
+
num_encoder_layers=config.transformer_enc_layers,
|
| 366 |
+
num_feature_levels=self.transformer_num_feature_levels,
|
| 367 |
+
)
|
| 368 |
+
|
| 369 |
+
N_steps = conv_dim // 2
|
| 370 |
+
self.pe_layer = PositionEmbeddingSine(N_steps, normalize=True)
|
| 371 |
+
|
| 372 |
+
self.mask_features = nn.Conv2d(conv_dim, mask_dim, kernel_size=1)
|
| 373 |
+
xavier_uniform_(self.mask_features.weight, gain=1)
|
| 374 |
+
constant_(self.mask_features.bias, 0)
|
| 375 |
+
|
| 376 |
+
# FPN lateral + output convs for res2 (1 level)
|
| 377 |
+
# Names adapter_1 / layer_1 match the detectron2 add_module pattern
|
| 378 |
+
self.adapter_1 = Conv2dWithNorm(feature_channels["res2"], conv_dim, kernel_size=1)
|
| 379 |
+
xavier_uniform_(self.adapter_1.weight, gain=1)
|
| 380 |
+
self.layer_1 = Conv2dWithNorm(conv_dim, conv_dim, kernel_size=3, padding=1, activation=F.relu)
|
| 381 |
+
xavier_uniform_(self.layer_1.weight, gain=1)
|
| 382 |
+
|
| 383 |
+
# Store as lists too (mirrors original lateral_convs / output_convs ordering)
|
| 384 |
+
self.lateral_convs = [self.adapter_1]
|
| 385 |
+
self.output_convs = [self.layer_1]
|
| 386 |
+
|
| 387 |
+
def forward_features(self, features):
|
| 388 |
+
srcs, pos = [], []
|
| 389 |
+
for idx, f in enumerate(self.transformer_in_features[::-1]):
|
| 390 |
+
x = features[f].float()
|
| 391 |
+
srcs.append(self.input_proj[idx](x))
|
| 392 |
+
pos.append(self.pe_layer(x))
|
| 393 |
+
|
| 394 |
+
y, spatial_shapes, level_start_index = self.transformer(srcs, pos)
|
| 395 |
+
bs = y.shape[0]
|
| 396 |
+
|
| 397 |
+
split_sizes = []
|
| 398 |
+
for i in range(self.transformer_num_feature_levels):
|
| 399 |
+
if i < self.transformer_num_feature_levels - 1:
|
| 400 |
+
split_sizes.append(int(level_start_index[i + 1] - level_start_index[i]))
|
| 401 |
+
else:
|
| 402 |
+
split_sizes.append(y.shape[1] - int(level_start_index[i]))
|
| 403 |
+
y = torch.split(y, split_sizes, dim=1)
|
| 404 |
+
|
| 405 |
+
out = []
|
| 406 |
+
for i, z in enumerate(y):
|
| 407 |
+
out.append(z.transpose(1, 2).view(bs, -1, int(spatial_shapes[i][0]), int(spatial_shapes[i][1])))
|
| 408 |
+
|
| 409 |
+
# FPN: add res2
|
| 410 |
+
fpn_features = [self.in_features[0]] # ["res2"]
|
| 411 |
+
for idx, f in enumerate(fpn_features[::-1]):
|
| 412 |
+
x = features[f].float()
|
| 413 |
+
cur_fpn = self.lateral_convs[idx](x)
|
| 414 |
+
merged = cur_fpn + F.interpolate(out[-1], size=cur_fpn.shape[-2:], mode="bilinear", align_corners=False)
|
| 415 |
+
out.append(self.output_convs[idx](merged))
|
| 416 |
+
|
| 417 |
+
multi_scale_features = []
|
| 418 |
+
for i, o in enumerate(out):
|
| 419 |
+
if i < self.maskformer_num_feature_levels:
|
| 420 |
+
multi_scale_features.append(o)
|
| 421 |
+
|
| 422 |
+
return self.mask_features(out[-1]), out[0], multi_scale_features
|
| 423 |
+
|
| 424 |
+
|
| 425 |
+
# ---------------------------------------------------------------------------
|
| 426 |
+
# Transformer decoder components
|
| 427 |
+
# ---------------------------------------------------------------------------
|
| 428 |
+
|
| 429 |
+
class MaskPooling(nn.Module):
|
| 430 |
+
def forward(self, x, mask):
|
| 431 |
+
if x.shape[-2:] != mask.shape[-2:]:
|
| 432 |
+
mask = F.interpolate(mask, size=x.shape[-2:], mode="bilinear", align_corners=False)
|
| 433 |
+
with torch.no_grad():
|
| 434 |
+
mask = (mask.detach() > 0).to(mask.dtype)
|
| 435 |
+
denorm = mask.sum(dim=(-1, -2), keepdim=True) + 1e-8
|
| 436 |
+
return torch.einsum("bchw,bqhw->bqc", x, mask / denorm)
|
| 437 |
+
|
| 438 |
+
|
| 439 |
+
class SelfAttentionLayer(nn.Module):
|
| 440 |
+
def __init__(self, d_model, nhead, dropout=0.0, normalize_before=False):
|
| 441 |
+
super().__init__()
|
| 442 |
+
self.self_attn = nn.MultiheadAttention(d_model, nhead, dropout=dropout)
|
| 443 |
+
self.norm = nn.LayerNorm(d_model)
|
| 444 |
+
self.dropout = nn.Dropout(dropout)
|
| 445 |
+
self.normalize_before = normalize_before
|
| 446 |
+
for p in self.parameters():
|
| 447 |
+
if p.dim() > 1:
|
| 448 |
+
nn.init.xavier_uniform_(p)
|
| 449 |
+
|
| 450 |
+
def with_pos_embed(self, tensor, pos):
|
| 451 |
+
return tensor if pos is None else tensor + pos
|
| 452 |
+
|
| 453 |
+
def forward(self, tgt, tgt_mask=None, tgt_key_padding_mask=None, query_pos=None):
|
| 454 |
+
if self.normalize_before:
|
| 455 |
+
tgt2 = self.norm(tgt)
|
| 456 |
+
q = k = self.with_pos_embed(tgt2, query_pos)
|
| 457 |
+
tgt2 = self.self_attn(q, k, value=tgt2, attn_mask=tgt_mask,
|
| 458 |
+
key_padding_mask=tgt_key_padding_mask)[0]
|
| 459 |
+
return tgt + self.dropout(tgt2)
|
| 460 |
+
q = k = self.with_pos_embed(tgt, query_pos)
|
| 461 |
+
tgt2 = self.self_attn(q, k, value=tgt, attn_mask=tgt_mask,
|
| 462 |
+
key_padding_mask=tgt_key_padding_mask)[0]
|
| 463 |
+
tgt = tgt + self.dropout(tgt2)
|
| 464 |
+
return self.norm(tgt)
|
| 465 |
+
|
| 466 |
+
|
| 467 |
+
class CrossAttentionLayer(nn.Module):
|
| 468 |
+
def __init__(self, d_model, nhead, dropout=0.0, normalize_before=False):
|
| 469 |
+
super().__init__()
|
| 470 |
+
self.multihead_attn = nn.MultiheadAttention(d_model, nhead, dropout=dropout)
|
| 471 |
+
self.norm = nn.LayerNorm(d_model)
|
| 472 |
+
self.dropout = nn.Dropout(dropout)
|
| 473 |
+
self.normalize_before = normalize_before
|
| 474 |
+
for p in self.parameters():
|
| 475 |
+
if p.dim() > 1:
|
| 476 |
+
nn.init.xavier_uniform_(p)
|
| 477 |
+
|
| 478 |
+
def with_pos_embed(self, tensor, pos):
|
| 479 |
+
return tensor if pos is None else tensor + pos
|
| 480 |
+
|
| 481 |
+
def forward(self, tgt, memory, memory_mask=None, memory_key_padding_mask=None,
|
| 482 |
+
pos=None, query_pos=None):
|
| 483 |
+
if self.normalize_before:
|
| 484 |
+
tgt2 = self.norm(tgt)
|
| 485 |
+
tgt2 = self.multihead_attn(
|
| 486 |
+
query=self.with_pos_embed(tgt2, query_pos),
|
| 487 |
+
key=self.with_pos_embed(memory, pos),
|
| 488 |
+
value=memory, attn_mask=memory_mask,
|
| 489 |
+
key_padding_mask=memory_key_padding_mask,
|
| 490 |
+
)[0]
|
| 491 |
+
return tgt + self.dropout(tgt2)
|
| 492 |
+
tgt2 = self.multihead_attn(
|
| 493 |
+
query=self.with_pos_embed(tgt, query_pos),
|
| 494 |
+
key=self.with_pos_embed(memory, pos),
|
| 495 |
+
value=memory, attn_mask=memory_mask,
|
| 496 |
+
key_padding_mask=memory_key_padding_mask,
|
| 497 |
+
)[0]
|
| 498 |
+
tgt = tgt + self.dropout(tgt2)
|
| 499 |
+
return self.norm(tgt)
|
| 500 |
+
|
| 501 |
+
|
| 502 |
+
class FFNLayer(nn.Module):
|
| 503 |
+
def __init__(self, d_model, dim_feedforward=2048, dropout=0.0, normalize_before=False):
|
| 504 |
+
super().__init__()
|
| 505 |
+
self.linear1 = nn.Linear(d_model, dim_feedforward)
|
| 506 |
+
self.dropout = nn.Dropout(dropout)
|
| 507 |
+
self.linear2 = nn.Linear(dim_feedforward, d_model)
|
| 508 |
+
self.norm = nn.LayerNorm(d_model)
|
| 509 |
+
self.normalize_before = normalize_before
|
| 510 |
+
for p in self.parameters():
|
| 511 |
+
if p.dim() > 1:
|
| 512 |
+
nn.init.xavier_uniform_(p)
|
| 513 |
+
|
| 514 |
+
def forward(self, tgt):
|
| 515 |
+
if self.normalize_before:
|
| 516 |
+
tgt2 = self.norm(tgt)
|
| 517 |
+
tgt2 = self.linear2(self.dropout(F.relu(self.linear1(tgt2))))
|
| 518 |
+
return tgt + self.dropout(tgt2)
|
| 519 |
+
tgt2 = self.linear2(self.dropout(F.relu(self.linear1(tgt))))
|
| 520 |
+
tgt = tgt + self.dropout(tgt2)
|
| 521 |
+
return self.norm(tgt)
|
| 522 |
+
|
| 523 |
+
|
| 524 |
+
class MLP(nn.Module):
|
| 525 |
+
def __init__(self, input_dim, hidden_dim, output_dim, num_layers):
|
| 526 |
+
super().__init__()
|
| 527 |
+
self.num_layers = num_layers
|
| 528 |
+
h = [hidden_dim] * (num_layers - 1)
|
| 529 |
+
self.layers = nn.ModuleList(
|
| 530 |
+
nn.Linear(n, k) for n, k in zip([input_dim] + h, h + [output_dim])
|
| 531 |
+
)
|
| 532 |
+
|
| 533 |
+
def forward(self, x):
|
| 534 |
+
for i, layer in enumerate(self.layers):
|
| 535 |
+
x = F.relu(layer(x)) if i < self.num_layers - 1 else layer(x)
|
| 536 |
+
return x
|
| 537 |
+
|
| 538 |
+
|
| 539 |
+
def get_classification_logits(x, text_classifier, logit_scale, num_templates):
|
| 540 |
+
x = F.normalize(x, dim=-1)
|
| 541 |
+
logit_scale = torch.clamp(logit_scale.exp(), max=100)
|
| 542 |
+
pred_logits = logit_scale * x @ text_classifier.T
|
| 543 |
+
final_pred_logits = []
|
| 544 |
+
cur_idx = 0
|
| 545 |
+
for num_t in num_templates:
|
| 546 |
+
final_pred_logits.append(pred_logits[:, :, cur_idx: cur_idx + num_t].max(-1).values)
|
| 547 |
+
cur_idx += num_t
|
| 548 |
+
final_pred_logits.append(pred_logits[:, :, -1])
|
| 549 |
+
return torch.stack(final_pred_logits, dim=-1)
|
| 550 |
+
|
| 551 |
+
|
| 552 |
+
# ---------------------------------------------------------------------------
|
| 553 |
+
# Transformer Decoder (Predictor)
|
| 554 |
+
# ---------------------------------------------------------------------------
|
| 555 |
+
|
| 556 |
+
class MultiScaleMaskedTransformerDecoder(nn.Module):
|
| 557 |
+
def __init__(self, config):
|
| 558 |
+
super().__init__()
|
| 559 |
+
hidden_dim = config.hidden_dim
|
| 560 |
+
num_queries = config.num_queries
|
| 561 |
+
nheads = config.nheads
|
| 562 |
+
dim_feedforward = config.dim_feedforward
|
| 563 |
+
dec_layers = config.dec_layers
|
| 564 |
+
pre_norm = config.pre_norm
|
| 565 |
+
mask_dim = config.mask_dim
|
| 566 |
+
clip_embedding_dim = config.clip_embedding_dim
|
| 567 |
+
|
| 568 |
+
N_steps = hidden_dim // 2
|
| 569 |
+
self.pe_layer = PositionEmbeddingSine(N_steps, normalize=True)
|
| 570 |
+
|
| 571 |
+
self.num_heads = nheads
|
| 572 |
+
self.num_layers = dec_layers
|
| 573 |
+
self.transformer_self_attention_layers = nn.ModuleList([
|
| 574 |
+
SelfAttentionLayer(hidden_dim, nheads, normalize_before=pre_norm)
|
| 575 |
+
for _ in range(dec_layers)
|
| 576 |
+
])
|
| 577 |
+
self.transformer_cross_attention_layers = nn.ModuleList([
|
| 578 |
+
CrossAttentionLayer(hidden_dim, nheads, normalize_before=pre_norm)
|
| 579 |
+
for _ in range(dec_layers)
|
| 580 |
+
])
|
| 581 |
+
self.transformer_ffn_layers = nn.ModuleList([
|
| 582 |
+
FFNLayer(hidden_dim, dim_feedforward, normalize_before=pre_norm)
|
| 583 |
+
for _ in range(dec_layers)
|
| 584 |
+
])
|
| 585 |
+
self.decoder_norm = nn.LayerNorm(hidden_dim)
|
| 586 |
+
|
| 587 |
+
self.num_queries = num_queries
|
| 588 |
+
self.query_feat = nn.Embedding(num_queries, hidden_dim)
|
| 589 |
+
self.query_embed = nn.Embedding(num_queries, hidden_dim)
|
| 590 |
+
|
| 591 |
+
self.num_feature_levels = 3
|
| 592 |
+
self.level_embed = nn.Embedding(self.num_feature_levels, hidden_dim)
|
| 593 |
+
# in_channels = conv_dim = 256 = hidden_dim → empty Sequential (no projection needed)
|
| 594 |
+
self.input_proj = nn.ModuleList([nn.Sequential() for _ in range(self.num_feature_levels)])
|
| 595 |
+
|
| 596 |
+
self.mask_embed = MLP(hidden_dim, hidden_dim, mask_dim, 3)
|
| 597 |
+
self.mask_pooling = MaskPooling()
|
| 598 |
+
self._mask_pooling_proj = nn.Sequential(
|
| 599 |
+
nn.LayerNorm(hidden_dim),
|
| 600 |
+
nn.Linear(hidden_dim, hidden_dim),
|
| 601 |
+
)
|
| 602 |
+
self.class_embed = MLP(hidden_dim, hidden_dim, clip_embedding_dim, 3)
|
| 603 |
+
self.logit_scale = nn.Parameter(torch.ones([]) * np.log(1 / 0.07))
|
| 604 |
+
|
| 605 |
+
def forward(self, x, mask_features, text_classifier, num_templates, mask=None):
|
| 606 |
+
assert len(x) == self.num_feature_levels
|
| 607 |
+
src, pos, size_list = [], [], []
|
| 608 |
+
for i in range(self.num_feature_levels):
|
| 609 |
+
size_list.append(x[i].shape[-2:])
|
| 610 |
+
pos.append(self.pe_layer(x[i], None).flatten(2))
|
| 611 |
+
src.append(
|
| 612 |
+
self.input_proj[i](x[i]).flatten(2)
|
| 613 |
+
+ self.level_embed.weight[i][None, :, None]
|
| 614 |
+
)
|
| 615 |
+
pos[-1] = pos[-1].permute(2, 0, 1)
|
| 616 |
+
src[-1] = src[-1].permute(2, 0, 1)
|
| 617 |
+
|
| 618 |
+
_, bs, _ = src[0].shape
|
| 619 |
+
query_embed = self.query_embed.weight.unsqueeze(1).repeat(1, bs, 1)
|
| 620 |
+
output = self.query_feat.weight.unsqueeze(1).repeat(1, bs, 1)
|
| 621 |
+
|
| 622 |
+
predictions_class, predictions_mask = [], []
|
| 623 |
+
outputs_class, outputs_mask, attn_mask = self._forward_prediction_heads(
|
| 624 |
+
output, mask_features, size_list[0], text_classifier, num_templates
|
| 625 |
+
)
|
| 626 |
+
predictions_class.append(outputs_class)
|
| 627 |
+
predictions_mask.append(outputs_mask)
|
| 628 |
+
|
| 629 |
+
for i in range(self.num_layers):
|
| 630 |
+
level_index = i % self.num_feature_levels
|
| 631 |
+
attn_mask[torch.where(attn_mask.sum(-1) == attn_mask.shape[-1])] = False
|
| 632 |
+
|
| 633 |
+
output = self.transformer_cross_attention_layers[i](
|
| 634 |
+
output, src[level_index],
|
| 635 |
+
memory_mask=attn_mask,
|
| 636 |
+
memory_key_padding_mask=None,
|
| 637 |
+
pos=pos[level_index], query_pos=query_embed,
|
| 638 |
+
)
|
| 639 |
+
output = self.transformer_self_attention_layers[i](
|
| 640 |
+
output, query_pos=query_embed
|
| 641 |
+
)
|
| 642 |
+
output = self.transformer_ffn_layers[i](output)
|
| 643 |
+
|
| 644 |
+
outputs_class, outputs_mask, attn_mask = self._forward_prediction_heads(
|
| 645 |
+
output, mask_features,
|
| 646 |
+
size_list[(i + 1) % self.num_feature_levels],
|
| 647 |
+
text_classifier, num_templates,
|
| 648 |
+
)
|
| 649 |
+
predictions_class.append(outputs_class)
|
| 650 |
+
predictions_mask.append(outputs_mask)
|
| 651 |
+
|
| 652 |
+
return {
|
| 653 |
+
"pred_logits": predictions_class[-1],
|
| 654 |
+
"pred_masks": predictions_mask[-1],
|
| 655 |
+
}
|
| 656 |
+
|
| 657 |
+
def _forward_prediction_heads(self, output, mask_features, attn_mask_target_size,
|
| 658 |
+
text_classifier, num_templates):
|
| 659 |
+
decoder_output = self.decoder_norm(output).transpose(0, 1)
|
| 660 |
+
mask_embed = self.mask_embed(decoder_output)
|
| 661 |
+
outputs_mask = torch.einsum("bqc,bchw->bqhw", mask_embed, mask_features)
|
| 662 |
+
|
| 663 |
+
maskpool_embeddings = self.mask_pooling(x=mask_features, mask=outputs_mask)
|
| 664 |
+
maskpool_embeddings = self._mask_pooling_proj(maskpool_embeddings)
|
| 665 |
+
class_embed = self.class_embed(maskpool_embeddings + decoder_output)
|
| 666 |
+
outputs_class = get_classification_logits(class_embed, text_classifier, self.logit_scale, num_templates)
|
| 667 |
+
|
| 668 |
+
attn_mask = F.interpolate(outputs_mask, size=attn_mask_target_size, mode="bilinear", align_corners=False)
|
| 669 |
+
attn_mask = (
|
| 670 |
+
attn_mask.sigmoid().flatten(2).unsqueeze(1).repeat(1, self.num_heads, 1, 1).flatten(0, 1) < 0.5
|
| 671 |
+
).bool().detach()
|
| 672 |
+
return outputs_class, outputs_mask, attn_mask
|
| 673 |
+
|
| 674 |
+
|
| 675 |
+
# ---------------------------------------------------------------------------
|
| 676 |
+
# FC-CLIP Head (connects pixel decoder + transformer decoder)
|
| 677 |
+
# ---------------------------------------------------------------------------
|
| 678 |
+
|
| 679 |
+
class FCCLIPHead(nn.Module):
|
| 680 |
+
def __init__(self, config):
|
| 681 |
+
super().__init__()
|
| 682 |
+
self.pixel_decoder = MSDeformAttnPixelDecoder(config)
|
| 683 |
+
self.predictor = MultiScaleMaskedTransformerDecoder(config)
|
| 684 |
+
|
| 685 |
+
def forward(self, features):
|
| 686 |
+
mask_features, _, multi_scale_features = self.pixel_decoder.forward_features(features)
|
| 687 |
+
return self.predictor(
|
| 688 |
+
multi_scale_features, mask_features,
|
| 689 |
+
text_classifier=features["text_classifier"],
|
| 690 |
+
num_templates=features["num_templates"],
|
| 691 |
+
)
|
| 692 |
+
|
| 693 |
+
|
| 694 |
+
# ---------------------------------------------------------------------------
|
| 695 |
+
# CLIP Backbone (frozen)
|
| 696 |
+
# ---------------------------------------------------------------------------
|
| 697 |
+
|
| 698 |
+
class CLIPBackbone(nn.Module):
|
| 699 |
+
"""Frozen OpenCLIP ConvNeXt-Large backbone."""
|
| 700 |
+
|
| 701 |
+
def __init__(self, model_name, pretrained):
|
| 702 |
+
super().__init__()
|
| 703 |
+
import open_clip
|
| 704 |
+
self.clip_model, _, _ = open_clip.create_model_and_transforms(model_name, pretrained=pretrained)
|
| 705 |
+
self.text_tokenizer = open_clip.get_tokenizer(model_name)
|
| 706 |
+
self._freeze()
|
| 707 |
+
|
| 708 |
+
def _freeze(self):
|
| 709 |
+
self.eval()
|
| 710 |
+
for p in self.clip_model.parameters():
|
| 711 |
+
p.requires_grad = False
|
| 712 |
+
|
| 713 |
+
@property
|
| 714 |
+
def dim_latent(self):
|
| 715 |
+
return self.clip_model.text_projection.shape[-1]
|
| 716 |
+
|
| 717 |
+
def forward(self, x):
|
| 718 |
+
self.eval()
|
| 719 |
+
with torch.no_grad():
|
| 720 |
+
return self._extract_features_convnext(x)
|
| 721 |
+
|
| 722 |
+
def _extract_features_convnext(self, x):
|
| 723 |
+
out = {}
|
| 724 |
+
x = self.clip_model.visual.trunk.stem(x)
|
| 725 |
+
out["stem"] = x.contiguous()
|
| 726 |
+
for i in range(4):
|
| 727 |
+
x = self.clip_model.visual.trunk.stages[i](x)
|
| 728 |
+
out[f"res{i+2}"] = x.contiguous()
|
| 729 |
+
out["clip_vis_dense"] = self.clip_model.visual.trunk.norm_pre(x).contiguous()
|
| 730 |
+
return out
|
| 731 |
+
|
| 732 |
+
def visual_prediction_forward(self, x, masks=None):
|
| 733 |
+
batch, num_query, channel = x.shape
|
| 734 |
+
x = x.reshape(batch * num_query, channel, 1, 1)
|
| 735 |
+
x = self.clip_model.visual.trunk.head(x)
|
| 736 |
+
x = self.clip_model.visual.head(x)
|
| 737 |
+
return x.view(batch, num_query, x.shape[-1])
|
| 738 |
+
|
| 739 |
+
def encode_text(self, text, normalize=False):
|
| 740 |
+
cast_dtype = self.clip_model.transformer.get_cast_dtype()
|
| 741 |
+
x = self.clip_model.token_embedding(text).to(cast_dtype)
|
| 742 |
+
x = x + self.clip_model.positional_embedding.to(cast_dtype)
|
| 743 |
+
# Handle both old (LND) and new (NLD, batch_first=True) open_clip conventions
|
| 744 |
+
transformer = self.clip_model.transformer
|
| 745 |
+
if getattr(transformer, "batch_first", False):
|
| 746 |
+
x = transformer(x, attn_mask=self.clip_model.attn_mask)
|
| 747 |
+
else:
|
| 748 |
+
x = x.permute(1, 0, 2)
|
| 749 |
+
x = transformer(x, attn_mask=self.clip_model.attn_mask)
|
| 750 |
+
x = x.permute(1, 0, 2)
|
| 751 |
+
x = self.clip_model.ln_final(x)
|
| 752 |
+
x = x[torch.arange(x.shape[0]), text.argmax(dim=-1)] @ self.clip_model.text_projection
|
| 753 |
+
return F.normalize(x, dim=-1) if normalize else x
|
| 754 |
+
|
| 755 |
+
def get_text_classifier(self, text_list, device):
|
| 756 |
+
self.eval()
|
| 757 |
+
with torch.no_grad():
|
| 758 |
+
tokens = self.text_tokenizer(text_list).to(device)
|
| 759 |
+
return self.encode_text(tokens, normalize=False)
|
| 760 |
+
|
| 761 |
+
|
| 762 |
+
# ---------------------------------------------------------------------------
|
| 763 |
+
# Main PreTrainedModel
|
| 764 |
+
# ---------------------------------------------------------------------------
|
| 765 |
+
|
| 766 |
+
class FCCLIPForPanopticSegmentation(PreTrainedModel):
|
| 767 |
+
"""FC-CLIP panoptic segmentation model (HuggingFace trust_remote_code).
|
| 768 |
+
|
| 769 |
+
Usage::
|
| 770 |
+
|
| 771 |
+
from transformers import AutoModel
|
| 772 |
+
model = AutoModel.from_pretrained("neerajaabhyankar/fc-clip", trust_remote_code=True)
|
| 773 |
+
# pixel_values: float32 RGB tensor [B, 3, H, W], normalised with CLIP means/stds
|
| 774 |
+
panoptic_seg, segments_info = model(pixel_values)[0]
|
| 775 |
+
"""
|
| 776 |
+
|
| 777 |
+
config_class = FCCLIPConfig
|
| 778 |
+
|
| 779 |
+
def __init__(self, config: FCCLIPConfig):
|
| 780 |
+
super().__init__(config)
|
| 781 |
+
self.backbone = CLIPBackbone(config.clip_model_name, config.clip_pretrained)
|
| 782 |
+
self.sem_seg_head = FCCLIPHead(config)
|
| 783 |
+
self.mask_pooling = MaskPooling()
|
| 784 |
+
self.void_embedding = nn.Embedding(1, config.clip_embedding_dim)
|
| 785 |
+
|
| 786 |
+
# Registered buffers for normalisation
|
| 787 |
+
self.register_buffer(
|
| 788 |
+
"pixel_mean",
|
| 789 |
+
torch.tensor(config.pixel_mean).view(-1, 1, 1),
|
| 790 |
+
persistent=False,
|
| 791 |
+
)
|
| 792 |
+
self.register_buffer(
|
| 793 |
+
"pixel_std",
|
| 794 |
+
torch.tensor(config.pixel_std).view(-1, 1, 1),
|
| 795 |
+
persistent=False,
|
| 796 |
+
)
|
| 797 |
+
|
| 798 |
+
# Cache text classifiers per class list
|
| 799 |
+
self._test_text_classifier = None
|
| 800 |
+
self._test_num_templates = None
|
| 801 |
+
|
| 802 |
+
# Build category overlap mask (all overlap since train==test for COCO)
|
| 803 |
+
num_cls = config.num_classes
|
| 804 |
+
self.register_buffer(
|
| 805 |
+
"category_overlapping_mask",
|
| 806 |
+
torch.ones(num_cls, dtype=torch.long),
|
| 807 |
+
persistent=False,
|
| 808 |
+
)
|
| 809 |
+
|
| 810 |
+
# ------------------------------------------------------------------
|
| 811 |
+
# Text classifier helpers
|
| 812 |
+
# ------------------------------------------------------------------
|
| 813 |
+
|
| 814 |
+
def _build_text_classifier(self, class_names, device):
|
| 815 |
+
"""Encode class_names with VILD templates, average, and normalise."""
|
| 816 |
+
templated = []
|
| 817 |
+
num_templates = []
|
| 818 |
+
for synonyms in class_names:
|
| 819 |
+
if isinstance(synonyms, str):
|
| 820 |
+
synonyms = [synonyms]
|
| 821 |
+
for syn in synonyms:
|
| 822 |
+
for tmpl in VILD_PROMPT:
|
| 823 |
+
templated.append(tmpl.format(syn))
|
| 824 |
+
num_templates.append(len(synonyms))
|
| 825 |
+
|
| 826 |
+
bs = 128
|
| 827 |
+
feats = []
|
| 828 |
+
for i in range(0, len(templated), bs):
|
| 829 |
+
feats.append(self.backbone.get_text_classifier(templated[i: i + bs], device).detach())
|
| 830 |
+
text_classifier = torch.cat(feats, 0)
|
| 831 |
+
|
| 832 |
+
text_classifier = text_classifier / text_classifier.norm(dim=-1, keepdim=True)
|
| 833 |
+
n_per = len(VILD_PROMPT)
|
| 834 |
+
text_classifier = text_classifier.reshape(
|
| 835 |
+
text_classifier.shape[0] // n_per, n_per, text_classifier.shape[-1]
|
| 836 |
+
).mean(1)
|
| 837 |
+
text_classifier = text_classifier / text_classifier.norm(dim=-1, keepdim=True)
|
| 838 |
+
return text_classifier, [1] * len(class_names)
|
| 839 |
+
|
| 840 |
+
def get_text_classifier(self, class_names=None, device=None):
|
| 841 |
+
if device is None:
|
| 842 |
+
device = self.pixel_mean.device
|
| 843 |
+
if class_names is None:
|
| 844 |
+
class_names = self.config.stuff_classes
|
| 845 |
+
|
| 846 |
+
if self._test_text_classifier is None:
|
| 847 |
+
tc, nt = self._build_text_classifier(class_names, device)
|
| 848 |
+
self._test_text_classifier = tc
|
| 849 |
+
self._test_num_templates = nt
|
| 850 |
+
|
| 851 |
+
return self._test_text_classifier, self._test_num_templates
|
| 852 |
+
|
| 853 |
+
def set_class_names(self, class_names):
|
| 854 |
+
"""Override vocabulary for open-vocabulary inference."""
|
| 855 |
+
self._test_text_classifier = None
|
| 856 |
+
self._test_num_templates = None
|
| 857 |
+
self.config.stuff_classes = class_names
|
| 858 |
+
self.config.num_classes = len(class_names)
|
| 859 |
+
self.config.num_thing_classes = 0 # treat all as stuff in custom mode
|
| 860 |
+
|
| 861 |
+
# ------------------------------------------------------------------
|
| 862 |
+
# Forward
|
| 863 |
+
# ------------------------------------------------------------------
|
| 864 |
+
|
| 865 |
+
def forward(self, pixel_values, class_names=None):
|
| 866 |
+
"""
|
| 867 |
+
Args:
|
| 868 |
+
pixel_values: float32 RGB tensor [B, 3, H, W] already normalised with CLIP
|
| 869 |
+
means/stds (or raw [0,255] images — call preprocess_image first).
|
| 870 |
+
class_names: optional list of class name strings to override vocabulary.
|
| 871 |
+
|
| 872 |
+
Returns:
|
| 873 |
+
list of (panoptic_seg, segments_info) tuples, one per image in batch.
|
| 874 |
+
panoptic_seg: int32 tensor [H, W] — pixel → segment id
|
| 875 |
+
segments_info: list[dict] with keys "id", "category_id", "isthing"
|
| 876 |
+
"""
|
| 877 |
+
if class_names is not None:
|
| 878 |
+
self.set_class_names(class_names)
|
| 879 |
+
|
| 880 |
+
images = pixel_values
|
| 881 |
+
h_pad, w_pad = images.shape[-2:]
|
| 882 |
+
|
| 883 |
+
# Run frozen backbone
|
| 884 |
+
features = self.backbone(images)
|
| 885 |
+
|
| 886 |
+
# Text classifier
|
| 887 |
+
text_classifier, num_templates = self.get_text_classifier(device=images.device)
|
| 888 |
+
text_classifier = text_classifier.to(images.device)
|
| 889 |
+
text_classifier = torch.cat(
|
| 890 |
+
[text_classifier, F.normalize(self.void_embedding.weight, dim=-1)], dim=0
|
| 891 |
+
)
|
| 892 |
+
features["text_classifier"] = text_classifier
|
| 893 |
+
features["num_templates"] = num_templates
|
| 894 |
+
|
| 895 |
+
# Segmentation head
|
| 896 |
+
outputs = self.sem_seg_head(features)
|
| 897 |
+
mask_cls_results = outputs["pred_logits"]
|
| 898 |
+
mask_pred_results = outputs["pred_masks"]
|
| 899 |
+
|
| 900 |
+
# Geometric ensemble (in-vocab + out-vocab)
|
| 901 |
+
clip_feature = features["clip_vis_dense"]
|
| 902 |
+
mask_for_pooling = F.interpolate(
|
| 903 |
+
mask_pred_results, size=clip_feature.shape[-2:], mode="bilinear", align_corners=False
|
| 904 |
+
)
|
| 905 |
+
pooled_clip_feature = self.mask_pooling(clip_feature, mask_for_pooling)
|
| 906 |
+
pooled_clip_feature = self.backbone.visual_prediction_forward(pooled_clip_feature)
|
| 907 |
+
out_vocab_cls_results = get_classification_logits(
|
| 908 |
+
pooled_clip_feature, text_classifier, self.backbone.clip_model.logit_scale, num_templates
|
| 909 |
+
)
|
| 910 |
+
|
| 911 |
+
in_vocab_cls_results = mask_cls_results[..., :-1]
|
| 912 |
+
out_vocab_cls_results = out_vocab_cls_results[..., :-1]
|
| 913 |
+
out_vocab_cls_probs = out_vocab_cls_results.softmax(-1)
|
| 914 |
+
in_vocab_cls_results = in_vocab_cls_results.softmax(-1)
|
| 915 |
+
|
| 916 |
+
cat_overlap = self.category_overlapping_mask.to(images.device)
|
| 917 |
+
alpha = self.config.geometric_ensemble_alpha
|
| 918 |
+
beta = self.config.geometric_ensemble_beta
|
| 919 |
+
|
| 920 |
+
cls_logits_seen = (
|
| 921 |
+
(in_vocab_cls_results ** (1 - alpha) * out_vocab_cls_probs ** alpha).log()
|
| 922 |
+
* cat_overlap
|
| 923 |
+
)
|
| 924 |
+
cls_logits_unseen = (
|
| 925 |
+
(in_vocab_cls_results ** (1 - beta) * out_vocab_cls_probs ** beta).log()
|
| 926 |
+
* (1 - cat_overlap)
|
| 927 |
+
)
|
| 928 |
+
cls_results = cls_logits_seen + cls_logits_unseen
|
| 929 |
+
|
| 930 |
+
is_void_prob = F.softmax(mask_cls_results, dim=-1)[..., -1:]
|
| 931 |
+
mask_cls_probs = torch.cat([
|
| 932 |
+
cls_results.softmax(-1) * (1.0 - is_void_prob),
|
| 933 |
+
is_void_prob,
|
| 934 |
+
], dim=-1)
|
| 935 |
+
mask_cls_results = torch.log(mask_cls_probs + 1e-8)
|
| 936 |
+
|
| 937 |
+
# Upsample masks to input resolution
|
| 938 |
+
mask_pred_results = F.interpolate(
|
| 939 |
+
mask_pred_results,
|
| 940 |
+
size=(h_pad, w_pad),
|
| 941 |
+
mode="bilinear",
|
| 942 |
+
align_corners=False,
|
| 943 |
+
)
|
| 944 |
+
|
| 945 |
+
results = []
|
| 946 |
+
for mask_cls_result, mask_pred_result in zip(mask_cls_results, mask_pred_results):
|
| 947 |
+
panoptic_r = self.panoptic_inference(mask_cls_result, mask_pred_result)
|
| 948 |
+
results.append(panoptic_r)
|
| 949 |
+
return results
|
| 950 |
+
|
| 951 |
+
def panoptic_inference(self, mask_cls, mask_pred):
|
| 952 |
+
scores, labels = F.softmax(mask_cls, dim=-1).max(-1)
|
| 953 |
+
mask_pred = mask_pred.sigmoid()
|
| 954 |
+
num_classes = self.config.num_classes
|
| 955 |
+
thing_ids = set(range(self.config.num_thing_classes))
|
| 956 |
+
|
| 957 |
+
keep = labels.ne(num_classes) & (scores > self.config.object_mask_threshold)
|
| 958 |
+
cur_scores = scores[keep]
|
| 959 |
+
cur_classes = labels[keep]
|
| 960 |
+
cur_masks = mask_pred[keep]
|
| 961 |
+
|
| 962 |
+
h, w = cur_masks.shape[-2:]
|
| 963 |
+
panoptic_seg = torch.zeros((h, w), dtype=torch.int32, device=cur_masks.device)
|
| 964 |
+
segments_info = []
|
| 965 |
+
|
| 966 |
+
if cur_masks.shape[0] == 0:
|
| 967 |
+
return panoptic_seg, segments_info
|
| 968 |
+
|
| 969 |
+
cur_prob_masks = cur_scores.view(-1, 1, 1) * cur_masks
|
| 970 |
+
cur_mask_ids = cur_prob_masks.argmax(0)
|
| 971 |
+
stuff_memory = {}
|
| 972 |
+
current_segment_id = 0
|
| 973 |
+
|
| 974 |
+
for k in range(cur_classes.shape[0]):
|
| 975 |
+
pred_class = cur_classes[k].item()
|
| 976 |
+
isthing = pred_class in thing_ids
|
| 977 |
+
mask_area = (cur_mask_ids == k).sum().item()
|
| 978 |
+
original_area = (cur_masks[k] >= 0.5).sum().item()
|
| 979 |
+
mask = (cur_mask_ids == k) & (cur_masks[k] >= 0.5)
|
| 980 |
+
|
| 981 |
+
if mask_area > 0 and original_area > 0 and mask.sum().item() > 0:
|
| 982 |
+
if mask_area / original_area < self.config.overlap_threshold:
|
| 983 |
+
continue
|
| 984 |
+
if not isthing:
|
| 985 |
+
if pred_class in stuff_memory:
|
| 986 |
+
panoptic_seg[mask] = stuff_memory[pred_class]
|
| 987 |
+
continue
|
| 988 |
+
else:
|
| 989 |
+
stuff_memory[pred_class] = current_segment_id + 1
|
| 990 |
+
|
| 991 |
+
current_segment_id += 1
|
| 992 |
+
panoptic_seg[mask] = current_segment_id
|
| 993 |
+
segments_info.append({
|
| 994 |
+
"id": current_segment_id,
|
| 995 |
+
"isthing": bool(isthing),
|
| 996 |
+
"category_id": int(pred_class),
|
| 997 |
+
})
|
| 998 |
+
|
| 999 |
+
return panoptic_seg, segments_info
|
| 1000 |
+
|
| 1001 |
+
# ------------------------------------------------------------------
|
| 1002 |
+
# Convenience preprocessing
|
| 1003 |
+
# ------------------------------------------------------------------
|
| 1004 |
+
|
| 1005 |
+
def preprocess_image(self, image):
|
| 1006 |
+
"""Normalise a uint8 RGB numpy array or PIL image to a model-ready tensor.
|
| 1007 |
+
|
| 1008 |
+
Returns float32 tensor [1, 3, H, W] on the same device as the model.
|
| 1009 |
+
"""
|
| 1010 |
+
import numpy as np
|
| 1011 |
+
if not isinstance(image, np.ndarray):
|
| 1012 |
+
image = np.array(image.convert("RGB"))
|
| 1013 |
+
tensor = torch.from_numpy(image).float().permute(2, 0, 1).unsqueeze(0)
|
| 1014 |
+
tensor = tensor.to(self.pixel_mean.device)
|
| 1015 |
+
return (tensor - self.pixel_mean) / self.pixel_std
|
preprocessor_config.json
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"image_processor_type": "BaseImageProcessor",
|
| 3 |
+
"do_normalize": true,
|
| 4 |
+
"image_mean": [0.48145466, 0.4578275, 0.40821073],
|
| 5 |
+
"image_std": [0.26862954, 0.26130258, 0.27577711],
|
| 6 |
+
"do_rescale": true,
|
| 7 |
+
"rescale_factor": 0.00392156862745098,
|
| 8 |
+
"do_resize": false,
|
| 9 |
+
"size": {"height": null, "width": null}
|
| 10 |
+
}
|