Image Segmentation
Transformers
Safetensors
metpredict_dpt
feature-extraction
pathology
dpt
custom_code
Instructions to use RendeiroLab/MetPredict-lung-structure-segmentation with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use RendeiroLab/MetPredict-lung-structure-segmentation with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("image-segmentation", model="RendeiroLab/MetPredict-lung-structure-segmentation", trust_remote_code=True)# Load model directly from transformers import AutoModel model = AutoModel.from_pretrained("RendeiroLab/MetPredict-lung-structure-segmentation", trust_remote_code=True, device_map="auto") - Notebooks
- Google Colab
- Kaggle
Upload folder using huggingface_hub
Browse files- README.md +52 -0
- config.json +40 -0
- dpt.py +507 -0
- hf_model.py +193 -0
- model.safetensors +3 -0
README.md
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
library_name: transformers
|
| 3 |
+
tags:
|
| 4 |
+
- image-segmentation
|
| 5 |
+
- pathology
|
| 6 |
+
- dpt
|
| 7 |
+
pipeline_tag: image-segmentation
|
| 8 |
+
---
|
| 9 |
+
|
| 10 |
+
# MetSeg — Macro-structure Segmentation (DPT)
|
| 11 |
+
|
| 12 |
+
Pathology macro-structure segmentation. DPT backbone with a custom HF wrapper
|
| 13 |
+
bundled via `trust_remote_code`.
|
| 14 |
+
|
| 15 |
+
## Usage
|
| 16 |
+
|
| 17 |
+
```python
|
| 18 |
+
from PIL import Image
|
| 19 |
+
import torch
|
| 20 |
+
from transformers import AutoModel
|
| 21 |
+
|
| 22 |
+
model = AutoModel.from_pretrained("RendeiroLab/MetPredict-lung-structure-segmentation", trust_remote_code=True).eval()
|
| 23 |
+
|
| 24 |
+
# Preprocess: PIL / ndarray / tensor — single image or list/batch.
|
| 25 |
+
# Applies ImageNet normalization (mean/std stored in config.json).
|
| 26 |
+
img = Image.open("tile.png")
|
| 27 |
+
pixel_values = model.preprocess(img) # → (1, 3, H, W) on model device
|
| 28 |
+
|
| 29 |
+
with torch.no_grad():
|
| 30 |
+
out = model(pixel_values)
|
| 31 |
+
logits = out.logits # (1, n_classes, H, W)
|
| 32 |
+
pred = logits.argmax(dim=1) # (1, H, W)
|
| 33 |
+
```
|
| 34 |
+
|
| 35 |
+
`preprocess` accepts:
|
| 36 |
+
- `PIL.Image` (RGB) — single or list
|
| 37 |
+
- `numpy.ndarray` shape `(H, W, 3)` or `(B, H, W, 3)`, uint8 or float
|
| 38 |
+
- `torch.Tensor` shape `(3, H, W)` or `(B, 3, H, W)`, uint8 or float
|
| 39 |
+
|
| 40 |
+
No resize is applied. H and W must be divisible by the backbone patch size
|
| 41 |
+
(typically 14 or 16). Tile or pad upstream as needed.
|
| 42 |
+
|
| 43 |
+
If you already have a normalized tensor, you can call the model directly with
|
| 44 |
+
`pixel_values=...`.
|
| 45 |
+
|
| 46 |
+
## Alternative: portable `torch.export`
|
| 47 |
+
|
| 48 |
+
```python
|
| 49 |
+
import torch
|
| 50 |
+
m = torch.export.load("model.pt2").module()
|
| 51 |
+
y = m(torch.randn(1, 3, 224, 224))
|
| 52 |
+
```
|
config.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"activation": null,
|
| 3 |
+
"architectures": [
|
| 4 |
+
"DPTForSegmentation"
|
| 5 |
+
],
|
| 6 |
+
"auto_map": {
|
| 7 |
+
"AutoConfig": "hf_model.DPTConfig",
|
| 8 |
+
"AutoModel": "hf_model.DPTForSegmentation"
|
| 9 |
+
},
|
| 10 |
+
"backbone": "hf-hub:bioptimus/H-optimus-0",
|
| 11 |
+
"class_names": [
|
| 12 |
+
"background",
|
| 13 |
+
"blood vessel",
|
| 14 |
+
"airway"
|
| 15 |
+
],
|
| 16 |
+
"decoder_fusion_channels": 224,
|
| 17 |
+
"decoder_intermediate_channels": [
|
| 18 |
+
224,
|
| 19 |
+
448,
|
| 20 |
+
896,
|
| 21 |
+
896
|
| 22 |
+
],
|
| 23 |
+
"decoder_readout": "cat",
|
| 24 |
+
"dtype": "float32",
|
| 25 |
+
"encoder_depth": 4,
|
| 26 |
+
"image_mean": [
|
| 27 |
+
0.485,
|
| 28 |
+
0.456,
|
| 29 |
+
0.406
|
| 30 |
+
],
|
| 31 |
+
"image_std": [
|
| 32 |
+
0.229,
|
| 33 |
+
0.224,
|
| 34 |
+
0.225
|
| 35 |
+
],
|
| 36 |
+
"in_channels": 3,
|
| 37 |
+
"model_type": "metpredict_dpt",
|
| 38 |
+
"n_classes": 3,
|
| 39 |
+
"transformers_version": "5.3.0"
|
| 40 |
+
}
|
dpt.py
ADDED
|
@@ -0,0 +1,507 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# %%
|
| 2 |
+
import warnings
|
| 3 |
+
from typing import Optional, Sequence, Union, Callable, Literal, Any
|
| 4 |
+
|
| 5 |
+
import torch
|
| 6 |
+
import torch.nn as nn
|
| 7 |
+
|
| 8 |
+
from segmentation_models_pytorch.base import (
|
| 9 |
+
ClassificationHead,
|
| 10 |
+
SegmentationModel,
|
| 11 |
+
)
|
| 12 |
+
from segmentation_models_pytorch.encoders.timm_vit import TimmViTEncoder
|
| 13 |
+
from segmentation_models_pytorch.base.utils import is_torch_compiling
|
| 14 |
+
from segmentation_models_pytorch.base.hub_mixin import supports_config_loading
|
| 15 |
+
from segmentation_models_pytorch.base.modules import Activation
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
class ReadoutConcatBlock(nn.Module):
|
| 19 |
+
"""
|
| 20 |
+
Concatenates the cls tokens with the features to make use of the global information aggregated in the prefix (cls) tokens.
|
| 21 |
+
Projects the combined feature map to the original embedding dimension using a MLP.
|
| 22 |
+
|
| 23 |
+
According to:
|
| 24 |
+
https://github.com/isl-org/DPT/blob/cd3fe90bb4c48577535cc4d51b602acca688a2ee/dpt/vit.py#L79-L90
|
| 25 |
+
"""
|
| 26 |
+
|
| 27 |
+
def __init__(self, embed_dim: int, has_prefix_tokens: bool):
|
| 28 |
+
super().__init__()
|
| 29 |
+
in_features = embed_dim * 2 if has_prefix_tokens else embed_dim
|
| 30 |
+
out_features = embed_dim
|
| 31 |
+
self.project = nn.Sequential(
|
| 32 |
+
nn.Linear(in_features, out_features),
|
| 33 |
+
nn.GELU(),
|
| 34 |
+
)
|
| 35 |
+
|
| 36 |
+
def forward(
|
| 37 |
+
self, features: torch.Tensor, prefix_tokens: Optional[torch.Tensor] = None
|
| 38 |
+
) -> torch.Tensor:
|
| 39 |
+
batch_size, embed_dim, height, width = features.shape
|
| 40 |
+
|
| 41 |
+
# Rearrange to (batch_size, height * width, embed_dim)
|
| 42 |
+
features = features.view(batch_size, embed_dim, -1)
|
| 43 |
+
features = features.transpose(1, 2).contiguous()
|
| 44 |
+
|
| 45 |
+
if prefix_tokens is not None:
|
| 46 |
+
# (batch_size, num_prefix_tokens, embed_dim) -> (batch_size, 1, embed_dim)
|
| 47 |
+
prefix_tokens = prefix_tokens[:, :1].expand_as(features)
|
| 48 |
+
features = torch.cat([features, prefix_tokens], dim=2)
|
| 49 |
+
|
| 50 |
+
# Project to embedding dimension
|
| 51 |
+
features = self.project(features)
|
| 52 |
+
|
| 53 |
+
# Rearrange back to (batch_size, embed_dim, height, width)
|
| 54 |
+
features = features.transpose(1, 2)
|
| 55 |
+
features = features.view(batch_size, -1, height, width)
|
| 56 |
+
|
| 57 |
+
return features
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
class ReadoutAddBlock(nn.Module):
|
| 61 |
+
"""
|
| 62 |
+
Adds the prefix tokens to the features to make use of the global information aggregated in the prefix (cls) tokens.
|
| 63 |
+
|
| 64 |
+
According to:
|
| 65 |
+
https://github.com/isl-org/DPT/blob/cd3fe90bb4c48577535cc4d51b602acca688a2ee/dpt/vit.py#L71-L76
|
| 66 |
+
"""
|
| 67 |
+
|
| 68 |
+
def forward(
|
| 69 |
+
self, features: torch.Tensor, prefix_tokens: Optional[torch.Tensor] = None
|
| 70 |
+
) -> torch.Tensor:
|
| 71 |
+
if prefix_tokens is not None:
|
| 72 |
+
batch_size, embed_dim, height, width = features.shape
|
| 73 |
+
prefix_tokens = prefix_tokens.mean(dim=1)
|
| 74 |
+
prefix_tokens = prefix_tokens.view(batch_size, embed_dim, 1, 1)
|
| 75 |
+
features = features + prefix_tokens
|
| 76 |
+
return features
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
class ReadoutIgnoreBlock(nn.Module):
|
| 80 |
+
"""
|
| 81 |
+
Ignores the prefix tokens and returns the features as is.
|
| 82 |
+
"""
|
| 83 |
+
|
| 84 |
+
def forward(self, features: torch.Tensor, *args, **kwargs) -> torch.Tensor:
|
| 85 |
+
return features
|
| 86 |
+
|
| 87 |
+
|
| 88 |
+
class ReassembleBlock(nn.Module):
|
| 89 |
+
"""
|
| 90 |
+
Processes the features such that they have progressively increasing embedding size and progressively decreasing
|
| 91 |
+
spatial dimension
|
| 92 |
+
"""
|
| 93 |
+
|
| 94 |
+
def __init__(
|
| 95 |
+
self,
|
| 96 |
+
in_channels: int,
|
| 97 |
+
mid_channels: int,
|
| 98 |
+
out_channels: int,
|
| 99 |
+
upsample_factor: int,
|
| 100 |
+
):
|
| 101 |
+
super().__init__()
|
| 102 |
+
|
| 103 |
+
self.project_to_out_channel = nn.Conv2d(
|
| 104 |
+
in_channels=in_channels,
|
| 105 |
+
out_channels=mid_channels,
|
| 106 |
+
kernel_size=1,
|
| 107 |
+
)
|
| 108 |
+
|
| 109 |
+
if upsample_factor > 1.0:
|
| 110 |
+
self.upsample = nn.ConvTranspose2d(
|
| 111 |
+
in_channels=mid_channels,
|
| 112 |
+
out_channels=mid_channels,
|
| 113 |
+
kernel_size=int(upsample_factor),
|
| 114 |
+
stride=int(upsample_factor),
|
| 115 |
+
)
|
| 116 |
+
elif upsample_factor == 1.0:
|
| 117 |
+
self.upsample = nn.Identity()
|
| 118 |
+
else:
|
| 119 |
+
self.upsample = nn.Conv2d(
|
| 120 |
+
in_channels=mid_channels,
|
| 121 |
+
out_channels=mid_channels,
|
| 122 |
+
kernel_size=3,
|
| 123 |
+
stride=int(1 / upsample_factor),
|
| 124 |
+
padding=1,
|
| 125 |
+
)
|
| 126 |
+
|
| 127 |
+
self.project_to_feature_dim = nn.Conv2d(
|
| 128 |
+
in_channels=mid_channels,
|
| 129 |
+
out_channels=out_channels,
|
| 130 |
+
kernel_size=3,
|
| 131 |
+
padding=1,
|
| 132 |
+
bias=False,
|
| 133 |
+
)
|
| 134 |
+
|
| 135 |
+
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
| 136 |
+
x = self.project_to_out_channel(x)
|
| 137 |
+
x = self.upsample(x)
|
| 138 |
+
x = self.project_to_feature_dim(x)
|
| 139 |
+
return x
|
| 140 |
+
|
| 141 |
+
|
| 142 |
+
class ResidualConvBlock(nn.Module):
|
| 143 |
+
def __init__(self, feature_dim: int):
|
| 144 |
+
super().__init__()
|
| 145 |
+
|
| 146 |
+
self.conv_1 = nn.Conv2d(
|
| 147 |
+
in_channels=feature_dim,
|
| 148 |
+
out_channels=feature_dim,
|
| 149 |
+
kernel_size=3,
|
| 150 |
+
padding=1,
|
| 151 |
+
bias=False,
|
| 152 |
+
)
|
| 153 |
+
self.batch_norm_1 = nn.BatchNorm2d(num_features=feature_dim)
|
| 154 |
+
self.conv_2 = nn.Conv2d(
|
| 155 |
+
in_channels=feature_dim,
|
| 156 |
+
out_channels=feature_dim,
|
| 157 |
+
kernel_size=3,
|
| 158 |
+
padding=1,
|
| 159 |
+
bias=False,
|
| 160 |
+
)
|
| 161 |
+
self.batch_norm_2 = nn.BatchNorm2d(num_features=feature_dim)
|
| 162 |
+
self.activation = nn.ReLU()
|
| 163 |
+
|
| 164 |
+
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
| 165 |
+
residual = x
|
| 166 |
+
|
| 167 |
+
# Block 1
|
| 168 |
+
x = self.activation(x)
|
| 169 |
+
x = self.conv_1(x)
|
| 170 |
+
x = self.batch_norm_1(x)
|
| 171 |
+
|
| 172 |
+
# Block 2
|
| 173 |
+
x = self.activation(x)
|
| 174 |
+
x = self.conv_2(x)
|
| 175 |
+
x = self.batch_norm_2(x)
|
| 176 |
+
|
| 177 |
+
# Add residual
|
| 178 |
+
x = x + residual
|
| 179 |
+
|
| 180 |
+
return x
|
| 181 |
+
|
| 182 |
+
|
| 183 |
+
class FusionBlock(nn.Module):
|
| 184 |
+
"""
|
| 185 |
+
Fuses the processed encoder features in a residual manner and upsamples them
|
| 186 |
+
"""
|
| 187 |
+
|
| 188 |
+
def __init__(self, feature_dim: int):
|
| 189 |
+
super().__init__()
|
| 190 |
+
self.residual_conv_block1 = ResidualConvBlock(feature_dim)
|
| 191 |
+
self.residual_conv_block2 = ResidualConvBlock(feature_dim)
|
| 192 |
+
self.project = nn.Conv2d(feature_dim, feature_dim, kernel_size=1)
|
| 193 |
+
self.activation = nn.ReLU()
|
| 194 |
+
|
| 195 |
+
def forward(
|
| 196 |
+
self,
|
| 197 |
+
feature: torch.Tensor,
|
| 198 |
+
previous_feature: Optional[torch.Tensor] = None,
|
| 199 |
+
) -> torch.Tensor:
|
| 200 |
+
feature = self.residual_conv_block1(feature)
|
| 201 |
+
if previous_feature is not None:
|
| 202 |
+
feature = feature + previous_feature
|
| 203 |
+
feature = self.residual_conv_block2(feature)
|
| 204 |
+
feature = nn.functional.interpolate(
|
| 205 |
+
feature, scale_factor=2, align_corners=True, mode="bilinear"
|
| 206 |
+
)
|
| 207 |
+
feature = self.project(feature)
|
| 208 |
+
return feature
|
| 209 |
+
|
| 210 |
+
|
| 211 |
+
class DPTDecoder(nn.Module):
|
| 212 |
+
"""
|
| 213 |
+
Decoder part for DPT
|
| 214 |
+
|
| 215 |
+
Processes the encoder features and class tokens (if encoder has class_tokens) to have spatial downsampling ratios of
|
| 216 |
+
[1/4, 1/8, 1/16, 1/32, ...] relative to the input image spatial dimension.
|
| 217 |
+
|
| 218 |
+
The decoder then fuses these features in a residual manner and progressively upsamples them by a factor of 2 so that the
|
| 219 |
+
output has a downsampling ratio of 1/2 relative to the input image spatial dimension
|
| 220 |
+
|
| 221 |
+
"""
|
| 222 |
+
|
| 223 |
+
def __init__(
|
| 224 |
+
self,
|
| 225 |
+
encoder_out_channels: Sequence[int] = (756, 756, 756, 756),
|
| 226 |
+
encoder_output_strides: Sequence[int] = (16, 16, 16, 16),
|
| 227 |
+
encoder_has_prefix_tokens: bool = True,
|
| 228 |
+
readout: Literal["cat", "add", "ignore"] = "cat",
|
| 229 |
+
intermediate_channels: Sequence[int] = (256, 512, 1024, 1024),
|
| 230 |
+
fusion_channels: int = 256,
|
| 231 |
+
):
|
| 232 |
+
super().__init__()
|
| 233 |
+
|
| 234 |
+
if not (
|
| 235 |
+
len(encoder_out_channels)
|
| 236 |
+
== len(encoder_output_strides)
|
| 237 |
+
== len(intermediate_channels)
|
| 238 |
+
):
|
| 239 |
+
raise ValueError(
|
| 240 |
+
"encoder_out_channels, encoder_output_strides and intermediate_channels must have the same length"
|
| 241 |
+
)
|
| 242 |
+
|
| 243 |
+
num_blocks = len(encoder_out_channels)
|
| 244 |
+
|
| 245 |
+
# If encoder has prefix tokens (e.g. cls_token), then we can concat/add/ignore them
|
| 246 |
+
# according to the readout mode
|
| 247 |
+
if readout == "cat":
|
| 248 |
+
blocks = [
|
| 249 |
+
ReadoutConcatBlock(in_channels, encoder_has_prefix_tokens)
|
| 250 |
+
for in_channels in encoder_out_channels
|
| 251 |
+
]
|
| 252 |
+
elif readout == "add":
|
| 253 |
+
blocks = [ReadoutAddBlock() for _ in encoder_out_channels]
|
| 254 |
+
elif readout == "ignore":
|
| 255 |
+
blocks = [ReadoutIgnoreBlock() for _ in encoder_out_channels]
|
| 256 |
+
else:
|
| 257 |
+
raise ValueError(
|
| 258 |
+
f"Invalid readout mode: {readout}, should be one of: 'cat', 'add', 'ignore'"
|
| 259 |
+
)
|
| 260 |
+
self.projection_blocks = nn.ModuleList(blocks)
|
| 261 |
+
|
| 262 |
+
# Upsample factors to resize features to progressively smaller scales
|
| 263 |
+
# For ViT models where all layers have the same stride, we create multi-scale features
|
| 264 |
+
# by progressively downsampling from the encoder output
|
| 265 |
+
scale_factors = []
|
| 266 |
+
min_stride = min(encoder_output_strides)
|
| 267 |
+
for i, stride in enumerate(encoder_output_strides):
|
| 268 |
+
# Progressive downsampling: i=0 keeps original, i=1 halves, i=2 quarters, etc.
|
| 269 |
+
target_stride = min_stride * (2 ** i)
|
| 270 |
+
scale_factor = stride / target_stride
|
| 271 |
+
scale_factors.append(scale_factor)
|
| 272 |
+
self.reassemble_blocks = nn.ModuleList()
|
| 273 |
+
for i in range(num_blocks):
|
| 274 |
+
block = ReassembleBlock(
|
| 275 |
+
in_channels=encoder_out_channels[i],
|
| 276 |
+
mid_channels=intermediate_channels[i],
|
| 277 |
+
out_channels=fusion_channels,
|
| 278 |
+
upsample_factor=scale_factors[i],
|
| 279 |
+
)
|
| 280 |
+
self.reassemble_blocks.append(block)
|
| 281 |
+
|
| 282 |
+
# Fusion blocks to fuse the processed features in a sequential manner
|
| 283 |
+
fusion_blocks = [FusionBlock(fusion_channels) for _ in range(num_blocks)]
|
| 284 |
+
self.fusion_blocks = nn.ModuleList(fusion_blocks)
|
| 285 |
+
|
| 286 |
+
def forward(
|
| 287 |
+
self, features: list[torch.Tensor], prefix_tokens: list[Optional[torch.Tensor]]
|
| 288 |
+
) -> torch.Tensor:
|
| 289 |
+
# Process the encoder features to scale of [1/4, 1/8, 1/16, 1/32, ...]
|
| 290 |
+
processed_features = []
|
| 291 |
+
for i, (feature, prefix_tokens_i) in enumerate(zip(features, prefix_tokens)):
|
| 292 |
+
projected_feature = self.projection_blocks[i](feature, prefix_tokens_i)
|
| 293 |
+
processed_feature = self.reassemble_blocks[i](projected_feature)
|
| 294 |
+
processed_features.append(processed_feature)
|
| 295 |
+
|
| 296 |
+
# Fusion and progressive upsampling starting from the last processed feature
|
| 297 |
+
processed_features = processed_features[::-1]
|
| 298 |
+
fused_feature = None
|
| 299 |
+
for fusion_block, feature in zip(self.fusion_blocks, processed_features):
|
| 300 |
+
fused_feature = fusion_block(feature, fused_feature)
|
| 301 |
+
|
| 302 |
+
return fused_feature
|
| 303 |
+
|
| 304 |
+
|
| 305 |
+
class DPTSegmentationHead(nn.Module):
|
| 306 |
+
def __init__(
|
| 307 |
+
self,
|
| 308 |
+
in_channels: int,
|
| 309 |
+
out_channels: int,
|
| 310 |
+
activation: Optional[Union[str, Callable]] = None,
|
| 311 |
+
kernel_size: int = 3,
|
| 312 |
+
upsampling: float = 2.0,
|
| 313 |
+
):
|
| 314 |
+
super().__init__()
|
| 315 |
+
|
| 316 |
+
self.head = nn.Sequential(
|
| 317 |
+
nn.Conv2d(
|
| 318 |
+
in_channels, in_channels, kernel_size=kernel_size, padding=1, bias=False
|
| 319 |
+
),
|
| 320 |
+
nn.BatchNorm2d(in_channels),
|
| 321 |
+
nn.ReLU(inplace=True),
|
| 322 |
+
nn.Dropout(p=0.1, inplace=False),
|
| 323 |
+
nn.Conv2d(in_channels, out_channels, kernel_size=1),
|
| 324 |
+
)
|
| 325 |
+
self.activation = Activation(activation)
|
| 326 |
+
self.upsampling_factor = upsampling
|
| 327 |
+
|
| 328 |
+
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
| 329 |
+
head_output = self.head(x)
|
| 330 |
+
resized_output = nn.functional.interpolate(
|
| 331 |
+
head_output,
|
| 332 |
+
scale_factor=self.upsampling_factor,
|
| 333 |
+
mode="bilinear",
|
| 334 |
+
align_corners=True,
|
| 335 |
+
)
|
| 336 |
+
activation_output = self.activation(resized_output)
|
| 337 |
+
return activation_output
|
| 338 |
+
|
| 339 |
+
|
| 340 |
+
class DPT(SegmentationModel):
|
| 341 |
+
"""
|
| 342 |
+
DPT is a dense prediction architecture that leverages vision transformers in place of convolutional networks as
|
| 343 |
+
a backbone for dense prediction tasks
|
| 344 |
+
|
| 345 |
+
It assembles tokens from various stages of the vision transformer into image-like representations at various resolutions
|
| 346 |
+
and progressively combines them into full-resolution predictions using a convolutional decoder.
|
| 347 |
+
|
| 348 |
+
The transformer backbone processes representations at a constant and relatively high resolution and has a global receptive
|
| 349 |
+
field at every stage. These properties allow the dense vision transformer to provide finer-grained and more globally coherent
|
| 350 |
+
predictions when compared to fully-convolutional networks
|
| 351 |
+
|
| 352 |
+
Note:
|
| 353 |
+
Since this model uses a Vision Transformer backbone, it typically requires a fixed input image size.
|
| 354 |
+
To handle variable input sizes, you can set `dynamic_img_size=True` in the model initialization
|
| 355 |
+
(if supported by the specific `timm` encoder). You can check if an encoder requires fixed size
|
| 356 |
+
using `model.encoder.is_fixed_input_size`, and get the required input dimensions from
|
| 357 |
+
`model.encoder.input_size`, however it's no guarantee that information is available.
|
| 358 |
+
|
| 359 |
+
Args:
|
| 360 |
+
encoder_name: Name of the classification model that will be used as an encoder (a.k.a backbone)
|
| 361 |
+
to extract features of different spatial resolution.
|
| 362 |
+
encoder_depth: A number of stages used in encoder in range [1,4]. Each stage generate features
|
| 363 |
+
smaller by a factor equal to the ViT model patch_size in spatial dimensions.
|
| 364 |
+
Default is 4.
|
| 365 |
+
encoder_weights: One of **None** (random initialization), or not **None** (pretrained weights would be loaded
|
| 366 |
+
with respect to the encoder_name, e.g. for ``"tu-vit_base_patch16_224.augreg_in21k"`` - ``"augreg_in21k"``
|
| 367 |
+
weights would be loaded).
|
| 368 |
+
encoder_output_indices: The indices of the encoder output features to use. If **None** will be sampled uniformly
|
| 369 |
+
across the number of blocks in encoder, e.g. if number of blocks is 4 and encoder has 20 blocks, then
|
| 370 |
+
encoder_output_indices will be (4, 9, 14, 19). If specified the number of indices should be equal to
|
| 371 |
+
encoder_depth. Default is **None**.
|
| 372 |
+
decoder_readout: The strategy to utilize the prefix tokens (e.g. cls_token) from the encoder.
|
| 373 |
+
Can be one of **"cat"**, **"add"**, or **"ignore"**. Default is **"cat"**.
|
| 374 |
+
decoder_intermediate_channels: The number of channels for the intermediate decoder layers. Reduce if you
|
| 375 |
+
want to reduce the number of parameters in the decoder. Default is (256, 512, 1024, 1024).
|
| 376 |
+
decoder_fusion_channels: The latent dimension to which the encoder features will be projected to before fusion.
|
| 377 |
+
Default is 256.
|
| 378 |
+
in_channels: Number of input channels for the model, default is 3 (RGB images)
|
| 379 |
+
classes: Number of classes for output mask (or you can think as a number of channels of output mask)
|
| 380 |
+
activation: An activation function to apply after the final convolution layer.
|
| 381 |
+
Available options are **"sigmoid"**, **"softmax"**, **"logsoftmax"**, **"tanh"**, **"identity"**,
|
| 382 |
+
**callable** and **None**. Default is **None**.
|
| 383 |
+
aux_params: Dictionary with parameters of the auxiliary output (classification head). Auxiliary output is build
|
| 384 |
+
on top of encoder if **aux_params** is not **None** (default). Supported params:
|
| 385 |
+
|
| 386 |
+
- **classes** (*int*): A number of classes;
|
| 387 |
+
- **pooling** (*str*): One of "max", "avg". Default is "avg";
|
| 388 |
+
- **dropout** (*float*): Dropout factor in [0, 1);
|
| 389 |
+
- **activation** (*str*): An activation function to apply "sigmoid"/"softmax" (could be **None** to return logits).
|
| 390 |
+
kwargs: Arguments passed to the encoder class ``__init__()`` function. Applies only to ``timm`` models. Keys with
|
| 391 |
+
``None`` values are pruned before passing. Specify ``dynamic_img_size=True`` to allow the model to handle images of different sizes.
|
| 392 |
+
|
| 393 |
+
Returns:
|
| 394 |
+
``torch.nn.Module``: DPT
|
| 395 |
+
|
| 396 |
+
"""
|
| 397 |
+
|
| 398 |
+
# fails for encoders with prefix tokens
|
| 399 |
+
_is_torch_scriptable = False
|
| 400 |
+
_is_torch_compilable = True
|
| 401 |
+
requires_divisible_input_shape = True
|
| 402 |
+
|
| 403 |
+
@supports_config_loading
|
| 404 |
+
def __init__(
|
| 405 |
+
self,
|
| 406 |
+
encoder_name: str = "hf-hub:paige-ai/Virchow2",
|
| 407 |
+
encoder_depth: int = 4,
|
| 408 |
+
encoder_output_indices: Optional[list[int]] = None,
|
| 409 |
+
decoder_readout: Literal["ignore", "add", "cat"] = "cat",
|
| 410 |
+
decoder_intermediate_channels: Sequence[int] = (224, 448, 896, 896),
|
| 411 |
+
decoder_fusion_channels: int = 224,
|
| 412 |
+
in_channels: int = 3,
|
| 413 |
+
classes: int = 1,
|
| 414 |
+
activation: Optional[Union[str, Callable]] = None,
|
| 415 |
+
aux_params: Optional[dict] = None,
|
| 416 |
+
**kwargs: dict[str, Any],
|
| 417 |
+
):
|
| 418 |
+
super().__init__()
|
| 419 |
+
|
| 420 |
+
if decoder_readout not in ["ignore", "add", "cat"]:
|
| 421 |
+
raise ValueError(
|
| 422 |
+
f"Invalid decoder readout mode. Must be one of: 'ignore', 'add', 'cat'. Got: {decoder_readout}"
|
| 423 |
+
)
|
| 424 |
+
|
| 425 |
+
from timm.layers import SwiGLUPacked
|
| 426 |
+
|
| 427 |
+
self.encoder = TimmViTEncoder(
|
| 428 |
+
name=encoder_name,
|
| 429 |
+
mlp_layer=SwiGLUPacked,
|
| 430 |
+
act_layer=torch.nn.SiLU,
|
| 431 |
+
in_channels=in_channels,
|
| 432 |
+
depth=encoder_depth,
|
| 433 |
+
output_indices=encoder_output_indices,
|
| 434 |
+
**kwargs,
|
| 435 |
+
)
|
| 436 |
+
|
| 437 |
+
if not self.encoder.has_prefix_tokens and decoder_readout != "ignore":
|
| 438 |
+
warnings.warn(
|
| 439 |
+
f"Encoder does not have prefix tokens (e.g. cls_token), but `decoder_readout` is set to '{decoder_readout}'. "
|
| 440 |
+
f"It's recommended to set `decoder_readout='ignore'` when using a encoder without prefix tokens.",
|
| 441 |
+
UserWarning,
|
| 442 |
+
)
|
| 443 |
+
|
| 444 |
+
self.decoder = DPTDecoder(
|
| 445 |
+
encoder_out_channels=self.encoder.out_channels,
|
| 446 |
+
encoder_output_strides=self.encoder.output_strides,
|
| 447 |
+
encoder_has_prefix_tokens=self.encoder.has_prefix_tokens,
|
| 448 |
+
readout=decoder_readout,
|
| 449 |
+
intermediate_channels=decoder_intermediate_channels,
|
| 450 |
+
fusion_channels=decoder_fusion_channels,
|
| 451 |
+
)
|
| 452 |
+
|
| 453 |
+
# Calculate required upsampling for segmentation head
|
| 454 |
+
# Decoder output spatial size: encoder_stride / (2^num_fusion_blocks)
|
| 455 |
+
# For ViT with stride 14 and 4 fusion blocks: 16 / (2^2) = 4, times 2 for each fusion = 32
|
| 456 |
+
# Actually: encoder_spatial = 224 / encoder_stride
|
| 457 |
+
# decoder_output_spatial = encoder_spatial / (2^(num_fusion_blocks-1))
|
| 458 |
+
# But we want output to match input, so:
|
| 459 |
+
# segmentation_upsampling = encoder_stride / 2 (to match input size)
|
| 460 |
+
# However, simpler: just use encoder stride as the upsampling factor
|
| 461 |
+
encoder_stride = max(self.encoder.output_strides) if self.encoder.output_strides else 14
|
| 462 |
+
seg_head_upsampling = float(encoder_stride / 2) # Upsample by encoder_stride/2 to reach input resolution
|
| 463 |
+
|
| 464 |
+
self.segmentation_head = DPTSegmentationHead(
|
| 465 |
+
in_channels=decoder_fusion_channels,
|
| 466 |
+
out_channels=classes,
|
| 467 |
+
activation=activation,
|
| 468 |
+
kernel_size=3,
|
| 469 |
+
upsampling=seg_head_upsampling,
|
| 470 |
+
)
|
| 471 |
+
|
| 472 |
+
if aux_params is not None:
|
| 473 |
+
self.classification_head = ClassificationHead(
|
| 474 |
+
in_channels=self.encoder.out_channels[-1], **aux_params
|
| 475 |
+
)
|
| 476 |
+
else:
|
| 477 |
+
self.classification_head = None
|
| 478 |
+
|
| 479 |
+
self.name = f"dpt-{encoder_name.split('/')[-1]}"
|
| 480 |
+
self.initialize()
|
| 481 |
+
|
| 482 |
+
def forward(self, x):
|
| 483 |
+
"""Sequentially pass `x` trough model`s encoder, decoder and heads"""
|
| 484 |
+
|
| 485 |
+
if not (
|
| 486 |
+
torch.jit.is_scripting() or torch.jit.is_tracing() or is_torch_compiling()
|
| 487 |
+
):
|
| 488 |
+
self.check_input_shape(x)
|
| 489 |
+
|
| 490 |
+
features, prefix_tokens = self.encoder(x)
|
| 491 |
+
decoder_output = self.decoder(features, prefix_tokens)
|
| 492 |
+
masks = self.segmentation_head(decoder_output)
|
| 493 |
+
# Ensure contiguous memory layout for DDP compatibility
|
| 494 |
+
masks = masks.contiguous()
|
| 495 |
+
|
| 496 |
+
if self.classification_head is not None:
|
| 497 |
+
labels = self.classification_head(features[-1])
|
| 498 |
+
return masks, labels
|
| 499 |
+
|
| 500 |
+
return masks
|
| 501 |
+
|
| 502 |
+
|
| 503 |
+
if __name__ == "__main__":
|
| 504 |
+
model = DPT()
|
| 505 |
+
img = torch.randn(1, 3, 224, 224)
|
| 506 |
+
out = model(img)
|
| 507 |
+
print(out.shape) # torch.Size([1, 1, 224, 224])
|
hf_model.py
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""HuggingFace `PreTrainedModel` + `PretrainedConfig` wrapper around `DPT`.
|
| 2 |
+
|
| 3 |
+
Lets consumers do `AutoModel.from_pretrained(repo_id, trust_remote_code=True)`
|
| 4 |
+
without importing the local `DPT` class. The `auto_map` field on the config
|
| 5 |
+
tells HF to bundle `hf_model.py` + `dpt.py` with the uploaded weights so the
|
| 6 |
+
classes are reconstructable in a clean env.
|
| 7 |
+
"""
|
| 8 |
+
from __future__ import annotations
|
| 9 |
+
|
| 10 |
+
from typing import Any, Literal, Optional, Sequence, Union, cast
|
| 11 |
+
|
| 12 |
+
import numpy as np
|
| 13 |
+
import torch
|
| 14 |
+
import torch.nn.functional as F
|
| 15 |
+
from transformers import (
|
| 16 |
+
AutoConfig,
|
| 17 |
+
AutoModel,
|
| 18 |
+
PretrainedConfig,
|
| 19 |
+
PreTrainedModel,
|
| 20 |
+
)
|
| 21 |
+
from transformers.modeling_outputs import SemanticSegmenterOutput
|
| 22 |
+
|
| 23 |
+
from .dpt import DPT
|
| 24 |
+
|
| 25 |
+
# ImageNet stats — matches the `A.Normalize()` default used at training time
|
| 26 |
+
# (correct for Virchow2 / H-optimus-0 backbones).
|
| 27 |
+
_IMAGENET_MEAN = (0.485, 0.456, 0.406)
|
| 28 |
+
_IMAGENET_STD = (0.229, 0.224, 0.225)
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
class DPTConfig(PretrainedConfig):
|
| 32 |
+
model_type = "metpredict_dpt"
|
| 33 |
+
|
| 34 |
+
def __init__(
|
| 35 |
+
self,
|
| 36 |
+
n_classes: int = 4,
|
| 37 |
+
class_names: Optional[list[str]] = None,
|
| 38 |
+
backbone: str = "hf-hub:bioptimus/H-optimus-0",
|
| 39 |
+
encoder_depth: int = 4,
|
| 40 |
+
decoder_intermediate_channels: tuple[int, ...] = (224, 448, 896, 896),
|
| 41 |
+
decoder_fusion_channels: int = 224,
|
| 42 |
+
decoder_readout: str = "cat",
|
| 43 |
+
activation: Optional[str] = None,
|
| 44 |
+
in_channels: int = 3,
|
| 45 |
+
image_mean: Sequence[float] = _IMAGENET_MEAN,
|
| 46 |
+
image_std: Sequence[float] = _IMAGENET_STD,
|
| 47 |
+
**kwargs,
|
| 48 |
+
):
|
| 49 |
+
super().__init__(**kwargs)
|
| 50 |
+
self.n_classes = n_classes
|
| 51 |
+
self.class_names = list(class_names) if class_names else []
|
| 52 |
+
self.backbone = backbone
|
| 53 |
+
self.encoder_depth = encoder_depth
|
| 54 |
+
self.decoder_intermediate_channels = list(decoder_intermediate_channels)
|
| 55 |
+
self.decoder_fusion_channels = decoder_fusion_channels
|
| 56 |
+
self.decoder_readout = decoder_readout
|
| 57 |
+
self.activation = activation
|
| 58 |
+
self.in_channels = in_channels
|
| 59 |
+
self.image_mean = list(image_mean)
|
| 60 |
+
self.image_std = list(image_std)
|
| 61 |
+
# `auto_map` makes the repo loadable as AutoModel without local imports.
|
| 62 |
+
self.auto_map = {
|
| 63 |
+
"AutoConfig": "hf_model.DPTConfig",
|
| 64 |
+
"AutoModel": "hf_model.DPTForSegmentation",
|
| 65 |
+
}
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
class DPTForSegmentation(PreTrainedModel):
|
| 69 |
+
config_class = DPTConfig
|
| 70 |
+
base_model_prefix = "dpt"
|
| 71 |
+
main_input_name = "pixel_values"
|
| 72 |
+
|
| 73 |
+
def __init__(self, config: DPTConfig):
|
| 74 |
+
super().__init__(config)
|
| 75 |
+
# `decoder_readout` is Literal-typed in DPT — cast since pydantic-loaded
|
| 76 |
+
# value is a plain str.
|
| 77 |
+
readout = cast(Literal["ignore", "add", "cat"], config.decoder_readout)
|
| 78 |
+
dpt_kwargs: dict[str, Any] = dict(
|
| 79 |
+
encoder_name=config.backbone,
|
| 80 |
+
encoder_depth=config.encoder_depth,
|
| 81 |
+
decoder_readout=readout,
|
| 82 |
+
decoder_intermediate_channels=tuple(config.decoder_intermediate_channels),
|
| 83 |
+
decoder_fusion_channels=config.decoder_fusion_channels,
|
| 84 |
+
in_channels=config.in_channels,
|
| 85 |
+
classes=config.n_classes,
|
| 86 |
+
activation=config.activation,
|
| 87 |
+
)
|
| 88 |
+
self.dpt = DPT(**dpt_kwargs)
|
| 89 |
+
# Normalize stats kept as buffers so they move with `.to(device)` and
|
| 90 |
+
# show up in `state_dict` for inspection but are excluded from grads.
|
| 91 |
+
self.register_buffer(
|
| 92 |
+
"image_mean",
|
| 93 |
+
torch.tensor(config.image_mean, dtype=torch.float32).view(1, -1, 1, 1),
|
| 94 |
+
persistent=False,
|
| 95 |
+
)
|
| 96 |
+
self.register_buffer(
|
| 97 |
+
"image_std",
|
| 98 |
+
torch.tensor(config.image_std, dtype=torch.float32).view(1, -1, 1, 1),
|
| 99 |
+
persistent=False,
|
| 100 |
+
)
|
| 101 |
+
# Skip post_init weight init — DPT.initialize() already ran inside DPT.__init__.
|
| 102 |
+
|
| 103 |
+
@torch.no_grad()
|
| 104 |
+
def preprocess(
|
| 105 |
+
self,
|
| 106 |
+
images: Union[
|
| 107 |
+
"PIL.Image.Image", # noqa: F821
|
| 108 |
+
np.ndarray,
|
| 109 |
+
torch.Tensor,
|
| 110 |
+
list,
|
| 111 |
+
],
|
| 112 |
+
) -> torch.Tensor:
|
| 113 |
+
"""Convert raw inputs into a model-ready ``pixel_values`` tensor.
|
| 114 |
+
|
| 115 |
+
Accepts a single image or a batch, in any of:
|
| 116 |
+
- PIL.Image (RGB)
|
| 117 |
+
- numpy.ndarray, shape (H, W, 3) or (B, H, W, 3), dtype uint8 or float
|
| 118 |
+
- torch.Tensor, shape (3, H, W) or (B, 3, H, W), dtype uint8 or float
|
| 119 |
+
|
| 120 |
+
Returns a float tensor of shape ``(B, 3, H, W)`` normalized with the
|
| 121 |
+
ImageNet stats used during training, on the model's current device.
|
| 122 |
+
|
| 123 |
+
Notes:
|
| 124 |
+
- uint8 inputs are scaled to [0, 1] before normalization.
|
| 125 |
+
- No resize is applied: the DPT is fully convolutional but the H and W
|
| 126 |
+
must be divisible by the backbone's patch size (typically 14 or 16).
|
| 127 |
+
Tile/pad upstream as needed.
|
| 128 |
+
"""
|
| 129 |
+
if isinstance(images, list):
|
| 130 |
+
tensors = [self._to_chw_float(x) for x in images]
|
| 131 |
+
batch = torch.stack(tensors, dim=0)
|
| 132 |
+
else:
|
| 133 |
+
t = self._to_chw_float(images)
|
| 134 |
+
batch = t if t.ndim == 4 else t.unsqueeze(0)
|
| 135 |
+
batch = batch.to(self.image_mean.device, dtype=torch.float32)
|
| 136 |
+
return (batch - self.image_mean) / self.image_std
|
| 137 |
+
|
| 138 |
+
@staticmethod
|
| 139 |
+
def _to_chw_float(x: Any) -> torch.Tensor:
|
| 140 |
+
"""Convert a single image-like input to a CHW float tensor in [0, 1]."""
|
| 141 |
+
# Lazy import: PIL is optional at inference time.
|
| 142 |
+
try:
|
| 143 |
+
from PIL import Image as _PILImage
|
| 144 |
+
except ImportError: # pragma: no cover
|
| 145 |
+
_PILImage = None # type: ignore[assignment]
|
| 146 |
+
if _PILImage is not None and isinstance(x, _PILImage.Image):
|
| 147 |
+
arr = np.asarray(x.convert("RGB")) # HWC uint8
|
| 148 |
+
t = torch.from_numpy(arr).permute(2, 0, 1).contiguous()
|
| 149 |
+
elif isinstance(x, np.ndarray):
|
| 150 |
+
t = torch.from_numpy(x)
|
| 151 |
+
if t.ndim == 3 and t.shape[-1] in (1, 3):
|
| 152 |
+
t = t.permute(2, 0, 1).contiguous()
|
| 153 |
+
elif t.ndim == 4 and t.shape[-1] in (1, 3):
|
| 154 |
+
t = t.permute(0, 3, 1, 2).contiguous()
|
| 155 |
+
elif isinstance(x, torch.Tensor):
|
| 156 |
+
t = x
|
| 157 |
+
else:
|
| 158 |
+
raise TypeError(f"Unsupported image type: {type(x).__name__}")
|
| 159 |
+
if t.dtype == torch.uint8:
|
| 160 |
+
t = t.float() / 255.0
|
| 161 |
+
else:
|
| 162 |
+
t = t.float()
|
| 163 |
+
return t
|
| 164 |
+
|
| 165 |
+
def forward(
|
| 166 |
+
self,
|
| 167 |
+
pixel_values: torch.Tensor,
|
| 168 |
+
labels: Optional[torch.Tensor] = None,
|
| 169 |
+
return_dict: bool = True,
|
| 170 |
+
):
|
| 171 |
+
logits = self.dpt(pixel_values)
|
| 172 |
+
loss: Optional[torch.Tensor] = None
|
| 173 |
+
if labels is not None:
|
| 174 |
+
loss = F.cross_entropy(logits, labels.long())
|
| 175 |
+
if not return_dict:
|
| 176 |
+
return (loss, logits) if loss is not None else (logits,)
|
| 177 |
+
# SemanticSegmenterOutput expects FloatTensor — cast suppresses Pylance.
|
| 178 |
+
return SemanticSegmenterOutput(
|
| 179 |
+
loss=cast(Any, loss),
|
| 180 |
+
logits=cast(Any, logits),
|
| 181 |
+
)
|
| 182 |
+
|
| 183 |
+
|
| 184 |
+
def _register() -> None:
|
| 185 |
+
try:
|
| 186 |
+
AutoConfig.register("metpredict_dpt", DPTConfig)
|
| 187 |
+
AutoModel.register(DPTConfig, DPTForSegmentation)
|
| 188 |
+
except ValueError:
|
| 189 |
+
# Already registered (re-import).
|
| 190 |
+
pass
|
| 191 |
+
|
| 192 |
+
|
| 193 |
+
_register()
|
model.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:5c7134fc04f842b056ebe5d4abe9113acaea76449642521549d2e6d4d8a3c49c
|
| 3 |
+
size 4746335316
|