File size: 4,398 Bytes
db10135
 
46b67b2
 
 
 
 
 
 
 
 
 
 
 
db10135
3c5ae45
 
 
 
5bda3ea
 
36b249e
5bda3ea
5ed337e
 
 
 
 
 
 
 
5bda3ea
 
 
36b249e
 
 
5bda3ea
36b249e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5bda3ea
36b249e
 
 
 
 
 
 
 
 
 
 
 
 
 
5bda3ea
 
36b249e
 
5bda3ea
36b249e
5bda3ea
 
36b249e
 
5bda3ea
36b249e
 
 
5bda3ea
36b249e
 
5bda3ea
36b249e
 
 
 
 
3c5ae45
36b249e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5bda3ea
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
---
license: apache-2.0
library_name: pytorch
tags:
  - pytorch
  - torchscript
  - dinov2
  - siamese-network
  - computer-vision
  - image-similarity
  - blueprint
  - architecture
  - hatch-pattern
pipeline_tag: image-feature-extraction
---


![example](https://cdn-uploads.huggingface.co/production/uploads/689a00a12b3976126e5e8431/LJW6BBA37P3m4EzrUnNRB.png)

# Siamese DINOv2 Wall Hatching Matcher

A TorchScript model for matching wall hatchings from architectural blueprints with legend patterns. Example of use in the repository.

## Model

- Backbone: DINOv2 ViT-B/14
- Architecture: Siamese network
- Framework: PyTorch
- Export: TorchScript
- Input size: 518 × 518

## Inference

```python
import json
from pathlib import Path

import torch
from PIL import Image, ImageDraw
from torchvision.transforms import InterpolationMode
from torchvision.transforms import functional as TF


ROOT = Path(__file__).resolve().parent
IMAGE_SIZE = 518
DEVICE = "cuda" if torch.cuda.is_available() else "cpu"


def prepare_image(filename: str, obb: list[float] | None = None):
    image = Image.open(ROOT / "images" / filename).convert("RGB")
    mask = Image.new("L", image.size, 0 if obb else 255)

    if obb:
        points = [
            (int(x), int(y))
            for x, y in zip(obb[::2], obb[1::2])
        ]
        ImageDraw.Draw(mask).polygon(points, fill=255)

    width, height = image.size
    scale = min(IMAGE_SIZE / width, IMAGE_SIZE / height)
    new_width = min(IMAGE_SIZE, round(width * scale))
    new_height = min(IMAGE_SIZE, round(height * scale))
    size = [new_height, new_width]

    image = TF.resize(
        image,
        size,
        interpolation=InterpolationMode.BILINEAR,
        antialias=True,
    )
    mask = TF.resize(
        mask,
        size,
        interpolation=InterpolationMode.NEAREST,
    )

    pad_x = IMAGE_SIZE - new_width
    pad_y = IMAGE_SIZE - new_height
    padding = [
        pad_x // 2,
        pad_y // 2,
        pad_x - pad_x // 2,
        pad_y - pad_y // 2,
    ]
    image = TF.pad(image, padding, fill=255)
    mask = TF.pad(mask, padding, fill=0)

    image = TF.to_tensor(image)
    image = TF.normalize(
        image,
        mean=[0.485, 0.456, 0.406],
        std=[0.229, 0.224, 0.225],
    )
    mask = TF.to_tensor(mask)

    return image.unsqueeze(0).to(DEVICE), mask.unsqueeze(0).to(DEVICE)


with (ROOT / "data.jsonl").open("r", encoding="utf-8") as file:
    items = [json.loads(line) for line in file if line.strip()]

model = torch.jit.load(ROOT / "dino_hatching.pt", map_location=DEVICE).eval()
wall_types = list(dict.fromkeys(item["wall_type"] for item in items))
walls = {name: prepare_image(name) for name in wall_types}

print("\nScore matrix")
print(" " * 6 + "".join(f"W{i}".rjust(8) for i in range(1, len(wall_types) + 1)))

with torch.inference_mode():
    for index, item in enumerate(items, start=1):
        plan_image, plan_mask = prepare_image(
            item["plan_image"],
            item["plan_obb"],
        )
        scores = []

        for wall_type in wall_types:
            wall_image, wall_mask = walls[wall_type]
            logit = model(wall_image, wall_mask, plan_image, plan_mask)
            scores.append(f"{torch.sigmoid(logit).item():.4f}")

        print(f"P{index:<5}" + "".join(f"{score:>8}" for score in scores))

print("\nRows:")
for index, item in enumerate(items, start=1):
    print(f"  P{index}: {item['plan_image']}")

print("Columns:")
for index, wall_type in enumerate(wall_types, start=1):
    print(f"  W{index}: {wall_type}")

# Score matrix
#             W1      W2      W3      W4      W5
# P1      0.9965  0.0005  0.0006  0.0044  0.0317
# P2      0.0026  0.9932  0.9916  0.0010  0.0028
# P3      0.0001  0.9984  0.9988  0.0001  0.0036
# P4      0.0005  0.0001  0.0002  0.9979  0.0002
# P5      0.0103  0.0004  0.0006  0.0001  0.9971

# Rows:
#   P1: valid_pair_000001_plan.png
#   P2: valid_pair_000004_plan.png
#   P3: valid_pair_000010_plan.png
#   P4: valid_pair_000013_plan.png
#   P5: valid_pair_000016_plan.png
# Columns:
#   W1: valid_pair_000001_legend.png
#   W2: valid_pair_000004_legend.png
#   W3: valid_pair_000010_legend.png
#   W4: valid_pair_000013_legend.png
#   W5: valid_pair_000016_legend.png
```

`score` is the probability that both hatchings belong to the same wall type.

- **1.0** → same wall type
- **0.0** → different wall type