Downloads · 30 days
7
6% of all-time downloads
pablo-moreira/puzzle-piece-sides-classifier
puzzle-piece-sides-classifier is a image classification model from pablo-moreira. Use it when you need a label for an image. It is set up for transformers. The card lists the license as cc-by-4.0.
A multi-task image classification model for identifying the four sides of a jigsaw puzzle piece and its complete side-pattern configuration.
Downloads · 30 days
7
6% of all-time downloads
All-time downloads
120
Public
Parameters
87.4M
699 MB on disk
Likes
0
Public
Click a slice to open those files.
.safetensors350 MB · 100%
From the Hugging Face model README
A multi-task image classification model for identifying the four sides of a jigsaw puzzle piece and its complete side-pattern configuration.
The model is based on DINOv2 Base and uses two classification heads:
This model is part of the PuzzleMap project.
| Property | Value |
|---|---|
| Architecture | DINOv2 Base + multi-task classification heads |
| Backbone | facebook/dinov2-base |
| Framework | PyTorch |
| Integration | Hugging Face Transformers |
| Number of sides | 4 |
| Side classes | 3 |
| Pattern classes | 60 output classes |
| Input size | 224 × 224 |
| Color format | RGBA/RGB converted by the image processor |
| Training strategy | Two-stage training |
| Augmentation | Rotation, mirroring, brightness, contrast, color, noise and blur |
The model is intended to classify individual jigsaw puzzle pieces after they have been detected and cropped from a puzzle image.
For each piece, the model predicts:
TOP
RIGHT
BOTTOM
LEFT
Each side can be one of:
SMOOTH — a flat puzzle border.OUTER — an outward protruding tab.INNER — an inward indentation.For example:
TOP = SMOOTH
RIGHT = OUTER
BOTTOM = INNER
LEFT = SMOOTH
The corresponding pattern is:
SOIS
where each character represents the first letter of its corresponding side class:
S = SMOOTH
O = OUTER
I = INNER
Therefore:
[SMOOTH, OUTER, INNER, SMOOTH] -> SOIS
The model uses facebook/dinov2-base as its visual backbone.
The CLS token from DINOv2 is used as the image representation:
Input Image
│
▼
DINOv2 Base
│
▼
CLS Embedding
│
├─────────────────────────────┐
│ │
▼ ▼
Sides Head Pattern Head
│ │
▼ ▼
4 × 3 logits 60 logits
│ │
▼ ▼
TOP/RIGHT/BOTTOM/LEFT Pattern
The sides head receives the DINOv2 hidden representation and produces:
4 × 3 = 12 logits
The output is reshaped to:
(batch_size, 4, 3)
The four positions correspond to:
0 = TOP
1 = RIGHT
2 = BOTTOM
3 = LEFT
Each position has three classes:
0 = SMOOTH
1 = OUTER
2 = INNER
The head architecture is:
LayerNorm
Linear(hidden_size → 512)
GELU
Dropout(0.2)
Linear(512 → 12)
The pattern head receives:
The side logits are detached before being concatenated with the DINOv2 representation:
pattern_input = torch.cat(
[
features,
sides_logits.detach().flatten(start_dim=1)
],
dim=1
)
The pattern head architecture is:
LayerNorm
Linear(hidden_size + 12 → 512)
GELU
Dropout(0.2)
Linear(512 → 60)
This creates a multi-task architecture where the model learns both the individual side characteristics and the global configuration of the piece.
The side classification uses three labels:
SMOOTH
OUTER
INNER
The pattern is represented by four characters.
Each character corresponds to one side:
TOP RIGHT BOTTOM LEFT
The complete set configured by the model is:
SSOI
SSIO
OSSI
...
For example:
SOIS
means:
TOP = SMOOTH
RIGHT = OUTER
BOTTOM = INNER
LEFT = SMOOTH
The conversion is performed by taking the first character of each side label.
def build_pattern_label(sides_labels):
return "".join([side[0] for side in sides_labels])
The model expects a cropped puzzle piece.
During training, the original piece was processed approximately as follows:
Original puzzle image
│
▼
Piece bounding box
│
▼
20% expanded bounding box
│
▼
Crop
│
▼
Resize to 224 × 224
│
▼
DINOv2 image processor
The expanded crop was used to preserve contextual pixels around the puzzle piece.
The final image is resized using the greatest dimension while preserving the aspect ratio and padding the remaining area with transparency.
Training was performed in two stages using the PuzzleMap dataset.
The dataset contains annotated jigsaw puzzle pieces used to train the model to classify the four sides of each piece and its complete side-pattern configuration.
The DINOv2 backbone was frozen.
Only the classification heads were trained.
Configuration:
Optimizer: AdamW
Learning rate: 1e-3
Batch size: 64
Maximum epochs: 30
Backbone: frozen
Pattern loss weight: 0.50
Early stopping patience: 4
The total loss is:
loss = sides_loss + pattern_weight × pattern_loss
For the released training configuration:
pattern_weight = 0.50
Both losses use cross entropy.
The complete model was then fine-tuned, including the DINOv2 backbone.
Configuration:
Optimizer: AdamW
Learning rate: 5e-6
Batch size: 16
Maximum epochs: 40
Backbone: trainable
Pattern loss weight: 0.50
Early stopping patience: 4
The best model was selected according to validation loss.
Install the required packages:
pip install torch torchvision transformers pillow
The model can be loaded directly using AutoModel.
Because this is a custom Transformers architecture, trust_remote_code=True is required.
from transformers import AutoModel, AutoImageProcessor
from PIL import Image
import torch
MODEL_ID = "pablo-moreira/puzzle-piece-sides-classifier"
processor = AutoImageProcessor.from_pretrained(
MODEL_ID,
trust_remote_code=True
)
model = AutoModel.from_pretrained(
MODEL_ID,
trust_remote_code=True
)
model.eval()
Load a cropped puzzle piece:
image = Image.open("puzzle_piece.png").convert("RGBA")
Process the image:
inputs = processor(
images=image,
return_tensors="pt"
)
Run the model:
with torch.no_grad():
outputs = model(**inputs)
The model returns two sets of logits:
outputs.sides_logits
outputs.pattern_logits
Their shapes are:
sides_logits:
(batch_size, 4, 3)
pattern_logits:
(batch_size, 60)
For a single image:
print(outputs.sides_logits.shape)
# torch.Size([1, 4, 3])
print(outputs.pattern_logits.shape)
# torch.Size([1, 60])
The processor provides helper methods to convert numeric predictions back to human-readable labels.
First, obtain the predicted side IDs:
sides_ids = outputs.predicted_sides_labels()[0]
Decode them:
sides = processor.decode_sides_labels(
[sides_ids.tolist()]
)[0]
print(sides)
Example:
['SMOOTH', 'OUTER', 'INNER', 'SMOOTH']
The order is always:
[
TOP,
RIGHT,
BOTTOM,
LEFT
]
You can also obtain the probabilities:
sides_probabilities = outputs.sides_probabilities()[0]
print(sides_probabilities.shape)
# torch.Size([4, 3])
For each side:
for side_index, probabilities in enumerate(sides_probabilities):
print(
side_index,
probabilities.tolist()
)
The class order is:
0 → SMOOTH
1 → OUTER
2 → INNER
Obtain the predicted pattern ID:
pattern_id = outputs.predicted_pattern_labels()[0].item()
Decode it using the processor:
pattern = processor.decode_pattern_labels(
[pattern_id]
)[0]
print(pattern)
Example:
SOIS
The pattern probability distribution can be obtained with:
pattern_probabilities = outputs.pattern_probabilities()[0]
print(pattern_probabilities.shape)
# torch.Size([60])
The following example shows how to run inference with the trained model using a sample puzzle-piece image from the PuzzleMap dataset.
The example downloads the image directly from Hugging Face, loads the trained model, processes the image, and displays the predicted side classification for each side of the puzzle piece.
from transformers import AutoModel, AutoImageProcessor
from PIL import Image
import torch
from io import BytesIO
import requests
MODEL_ID = "pablo-moreira/puzzle-piece-sides-classifier"
IMAGES = [
"https://huggingface.co/datasets/pablo-moreira/puzzle-map/resolve/main/samples/puzzle-piece-sides-classifier/120_avengers_20260709_105353_d1ee901639e04ba7972a6505cb07d541_OOII.png",
"https://huggingface.co/datasets/pablo-moreira/puzzle-map/resolve/main/samples/puzzle-piece-sides-classifier/120_avengers_20260709_110848_3b131fdfeb134505b994c6c7cab791ad_IIOO.png",
"https://huggingface.co/datasets/pablo-moreira/puzzle-map/resolve/main/samples/puzzle-piece-sides-classifier/120_avengers_20260709_112115_5a0ca30002bf48dfb97b9184f038cf30_IISI.png",
"https://huggingface.co/datasets/pablo-moreira/puzzle-map/resolve/main/samples/puzzle-piece-sides-classifier/camera_2c545dac37064e56aea6139df0951608_SOIS.png",
"https://huggingface.co/datasets/pablo-moreira/puzzle-map/resolve/main/samples/puzzle-piece-sides-classifier/puzzle-focus_d378d8e8-20240108_211624.redimensionado_ISSO.png"
]
# Load processor
processor = AutoImageProcessor.from_pretrained(
MODEL_ID,
trust_remote_code=True
)
# Load model
model = AutoModel.from_pretrained(
MODEL_ID,
trust_remote_code=True
)
model.eval()
# Load image
response = requests.get(IMAGES[0])
response.raise_for_status()
image = Image.open(BytesIO(response.content)).convert("RGB")
# Prepare input
inputs = processor(
images=image,
return_tensors="pt"
)
# Inference
with torch.no_grad():
outputs = model(**inputs)
# --------------------------------------------------
# Sides
# --------------------------------------------------
sides_ids = outputs.predicted_sides_labels()[0].tolist()
sides = processor.decode_sides_labels(
[sides_ids]
)[0]
print("Sides:")
print(f"TOP: {sides[0]}")
print(f"RIGHT: {sides[1]}")
print(f"BOTTOM: {sides[2]}")
print(f"LEFT: {sides[3]}")
# --------------------------------------------------
# Pattern
# --------------------------------------------------
pattern_id = outputs.predicted_pattern_labels()[0].item()
pattern = processor.decode_pattern_labels(
[pattern_id]
)[0]
print()
print("Pattern:")
print(pattern)
Example output:
Sides:
TOP: INNER
RIGHT: SMOOTH
BOTTOM: SMOOTH
LEFT: OUTER
Pattern:
ISSO
The processor also provides a helper for converting a pattern string into the corresponding side labels.
sides = processor.convert_pattern_to_sides("SOIS")
print(sides)
Result:
[
"SMOOTH",
"OUTER",
"INNER",
"SMOOTH"
]
The order is:
TOP → RIGHT → BOTTOM → LEFT
This can be useful when only the pattern prediction is required.
The model output is a PuzzlePieceSidesClassifierOutput.
It exposes:
outputs.sides_logits
outputs.pattern_logits
The output object also provides convenience methods:
outputs.sides_probabilities()
outputs.pattern_probabilities()
outputs.predicted_sides_labels()
outputs.predicted_pattern_labels()
For example:
sides_probabilities = outputs.sides_probabilities()
pattern_probabilities = outputs.pattern_probabilities()
sides_predictions = outputs.predicted_sides_labels()
pattern_predictions = outputs.predicted_pattern_labels()
The model has several important limitations.
The model expects an image containing an individual puzzle piece.
Performance may degrade if:
The training pipeline explicitly generates the four principal orientations:
0°
90°
180°
270°
plus a small random rotation perturbation.
Very large arbitrary rotations or unusual perspective distortions may not be represented adequately by the training distribution.
The model predicts a predefined set of pattern configurations.
It should not be assumed that an arbitrary four-side combination is represented by a unique class.
The repository contains the Hugging Face model artifacts required to load the model.
The custom architecture is exposed through:
puzzle_piece_sides_classifier.py
and the custom processor through:
puzzle_piece_sides_classifier_processor.py
The model weights are stored using SafeTensors.
The configuration and processor configuration are saved using the standard Hugging Face save_pretrained() mechanism.
The repository can therefore be loaded using:
AutoModel.from_pretrained(
"pablo-moreira/puzzle-piece-sides-classifier",
trust_remote_code=True
)
and:
AutoImageProcessor.from_pretrained(
"pablo-moreira/puzzle-piece-sides-classifier",
trust_remote_code=True
)
The following table compares the results from versions of the Puzzle Piece Sides and Pattern Classifier.
| Metric | v11 | v12 | v13 | v14 | v15 | v16 | v17 | v18 | v19 | v20 | v21 |
|---|---|---|---|---|---|---|---|---|---|---|---|
| Dataset | 518 | 518 | 2072 | 2080 | 3648 | 4664 | 6824 | 7160 | 7784 | 7784 | 9472 |
| Training examples | 414 | 414 | 1657 | 1664 | 2918 | 3731 | 5459 | 5728 | 6227 | 6227 | 7577 |
| Validation examples | 104 | 104 | 415 | 416 | 730 | 933 | 1365 | 1432 | 1557 | 1557 | 1895 |
| Pattern weight | 0.35 | 0.50 | 0.5 | 0.5 | 0.5 | 0.5 | 0.5 | 0.5 | 0.5 | 0.5 | 0.5 |
| Best epoch | 14 | 11 | 9 | 9 | 8 | 6 | 6 | 16 | 10 | 14 | 8 |
| Train loss | 0.0419 | 0.1039 | 0.0134 | 0.0060 | 0.0041 | 0.0080 | 0.0136 | 0.0049 | 0.0011 | 0.0106 | 0.0235 |
| Train sides loss | 0.0178 | 0.0455 | 0.0031 | 0.0007 | 0.0006 | 0.0009 | 0.0034 | 0.0011 | 0.0001 | 0.0044 | 0.0089 |
| Train pattern loss | 0.0688 | 0.1168 | 0.0206 | 0.0106 | 0.0069 | 0.0142 | 0.0204 | 0.0076 | 0.0019 | 0.0124 | 0.0291 |
| Train sides accuracy | 0.9982 | 0.9886 | 0.9995 | 1.0000 | 1.0000 | 1.0000 | 0.9992 | 0.9996 | 1.0000 | 0.9991 | 0.9984 |
| Train pattern accuracy | 0.9952 | 0.9808 | 0.9982 | 1.0000 | 0.9993 | 0.9997 | 0.9965 | 0.9988 | 1.0000 | 0.9979 | 0.9933 |
| Validation loss | 0.3734 | 0.3613 | 0.0634 | 0.0625 | 0.0285 | 0.0246 | 0.0212 | 0.0148 | 0.0052 | 0.0038 | 0.0011 |
| Validation sides loss | 0.1471 | 0.1156 | 0.0304 | 0.0299 | 0.0092 | 0.0084 | 0.0085 | 0.0078 | 0.0022 | 0.0019 | 0.0005 |
| Validation pattern loss | 0.6465 | 0.4914 | 0.0661 | 0.0652 | 0.0387 | 0.0325 | 0.0253 | 0.0139 | 0.0059 | 0.0038 | 0.0012 |
| Validation sides accuracy | 0.9815 | 0.9630 | 0.9939 | 0.9976 | 0.9986 | 0.9989 | 0.9987 | 0.9983 | 0.9990 | 0.9992 | 0.9999 |
| Validation pattern accuracy | 0.8426 | 0.9167 | 0.9904 | 0.9928 | 0.9918 | 0.9979 | 0.9978 | 0.9951 | 0.9987 | 0.9987 | 1.0000 |
| Validation sides F1 | 0.9998 | ||||||||||
| Validation pattern F1 | 1.0000 |
#9472#77840000#7784#7160#6824#4664#3648#20800.500.35This model is one component of the PuzzleMap computer-vision pipeline.
The broader project uses computer vision and machine learning to:
The sides classifier provides structured information that can be used by subsequent puzzle-solving components.
If you use this model in your project, please reference the PuzzleMap project and this model repository:
Pablo Moreira.
Puzzle Piece Sides Classifier.
PuzzleMap project
This model is released under the terms specified by the repository license.
The underlying facebook/dinov2-base model is subject to its own license and terms of use.
Users are responsible for verifying the licensing requirements of the underlying datasets, pretrained models and other dependencies used in their applications.