CataNET-Terrain-v1.0-Large

A dual-head MobileNetV3-Large classifier that reads a single, top-down-rectified Catan hex tile patch and predicts both its terrain type (6 classes) and resource number token (13-way head; 12 real values plus a placeholder slot) in one forward pass. This is the second stage of the CataNET pipeline, consuming patches extracted by the CataNET-Meridian pose model.

This is the larger of the two CataNET-Terrain variants β€” see CataNET-Terrain-v1.0-Small for a smaller/faster backbone.

Table of Contents

Model Details

Model Description

CataNET-Terrain-v1.0-Large is a dual-head classifier built on the mobilenet_v3_large architecture (via torchvision), trained from scratch β€” not fine-tuned from ImageNet or any other pretrained checkpoint. The shared MobileNetV3 feature extractor feeds two independent classification heads:

  • Terrain head: 6 classes β€” desert, fields, forest, hills, mountains, pasture
  • Token head: 13 classes β€” none, robber, 2-6, 7 (unused placeholder, never a valid label), 8-12

Input is expected to be a single 224Γ—224 top-down-rectified crop of one hex tile (i.e. already isolated and perspective-corrected β€” not a full board photo).

  • Developed by: nithinmanoj10
  • Model type: Multi-task (dual-head) image classification
  • License: MIT
  • Finetuned from model: None β€” trained from randomly-initialized mobilenet_v3_large weights, no pretrained backbone.

Uses

Direct Use

Classifies a single, pre-cropped, top-down hex patch. Not intended to be run on a raw board photo directly β€” patches must first be extracted and perspective-rectified by an upstream pose/detection stage (see CataNET-Meridian). See How to Get Started, and note the required inference-mode workaround in Limitations before integrating this model.

Downstream Use

Intended as the terrain/token classification stage of the full CataNET pipeline: pose detection β†’ per-hex patch extraction (homography-based rectification) β†’ this model β†’ structured board-state assembly, alongside a separate piece-color classifier for settlements/cities/roads.

Out-of-Scope Use

  • Not intended for anything other than the standard 19-hex Settlers of Catan base game board's terrain/token vocabulary.
  • Not a general-purpose texture or material classifier.
  • Does not detect hex position/geometry (see CataNET-Meridian) or player pieces (see the companion Lynx MicroCNN piece-color classifier).

Limitations

  • Requires model.train() at inference time, not model.eval() β€” this is intentional, not an oversight, but it's easy to get wrong. The checkpoint was validated (and is used in production) with the model left in training mode (only nn.Dropout layers forced to .eval()), because BatchNorm behaves incorrectly with this checkpoint under standard .eval() mode. Critically, this means BatchNorm uses per-batch statistics β€” running inference with a batch size of 1 will produce degenerate, near-constant output (in internal testing, this collapsed predictions to a single class regardless of input). Always run inference on a real batch of multiple patches at once (e.g. all hexes from one board), never a lone single-image batch.
  • Token accuracy is substantially better than raw numbers alone suggest, but check your ground truth. In this model's own evaluation, roughly 20% of real-photo instances had to be excluded from token scoring because the token was cropped/illegible in that specific patch β€” not because of a model failure. If you're benchmarking this model yourself, budget for the same issue.
  • The robber class is a known, consistent weak point. In evaluation, robber-occupied tiles were misclassified as "no token" (none) in every single tested instance (0/6). This is likely a sim-to-real gap: the synthetic training data's robber piece asset probably doesn't closely resemble how a physical robber piece looks in a real photo.
  • forest/mountains terrain and 2/3 tokens are the next-weakest classes β€” see Evaluation for the specific confusion pattern.
  • Trained entirely on synthetic data, composited from procedurally-generated terrain patches β€” a sim-to-real gap should be assumed beyond what's captured in the (small) real-photo evaluation set below.

Training Details

Training Data

Trained on the terrain-patch portion of nithinmanoj10/CatanSynth-Terrain-v1-50K β€” procedurally-generated, pre-rectified 224Γ—224 hex tile crops, each labeled with its ground-truth terrain and token (the label is encoded directly in each file's name and mirrored in a labels.csv). See the dataset card for exact split sizes and class distribution.

Training Procedure

Preprocessing

Inputs are normalized with dataset-specific (not ImageNet) statistics β€” use these exact values, not the standard ImageNet mean/std:

MEAN = [0.7209611535072327, 0.5950123071670532, 0.4485519230365753]
STD = [0.2021966278553009, 0.20071130990982056, 0.19387811422348022]

Training hyperparameters

Setting Value
Backbone mobilenet_v3_large (torchvision), trained from scratch
Batch size 512
Epochs 80 (early stopping patience: 7, on combined validation accuracy)
Optimizer AdamW, lr=2e-3, weight decay 1e-4
LR schedule Linear warmup (5 epochs) β†’ cosine annealing
Loss Weighted sum of both heads' cross-entropy: 0.25 * terrain_loss + 0.75 * token_loss (token reading treated as the harder task)
Sampling WeightedRandomSampler, combining inverse-frequency weights for both terrain and token, to counter real class imbalance (e.g. desert/robber are rare relative to numbered tiles)

Evaluation

Testing Data

Evaluated on 120 real hex-tile patches, extracted (via ground-truth keypoints) from the same 12 real Catan board photographs used to evaluate the CataNET-Meridian pose models, hand-labeled for terrain type and resource token. Labels went through a correction pass after an initial labeling error was caught (menu-position mixups specifically affecting low-value tokens) and were re-verified against the raw images before this evaluation was finalized.

Metrics

  • Terrain / Token accuracy β€” top-1 accuracy per head.
  • Terrain / Token macro-F1 β€” unweighted mean of per-class F1, chosen specifically because both heads have real class imbalance (see Training Procedure) that raw accuracy alone would mask.
  • Joint accuracy β€” fraction of patches where both heads are simultaneously correct.

Results

Metric Value
Terrain accuracy 0.692
Terrain macro-F1 0.720
Token accuracy* 0.802
Token macro-F1* 0.609
Joint accuracy (both correct)* 0.646

* Computed on 96/120 patches; 24 were excluded where the number token was cropped/illegible in that specific patch (not scoreable against any model).

Summary

desert and pasture terrain, and tokens 8-12, are classified reliably. The model confuses forest↔mountains terrain, 3↔2 tokens, and never correctly identifies a robber-occupied tile (0/6) β€” see Limitations. N=120 patches / 12 photos, selected for clean detections rather than randomly sampled β€” sufficient to characterize behavior, not a large-scale benchmark.

How to Get Started with the Model

import torch
import torch.nn as nn
from huggingface_hub import hf_hub_download
from torchvision.models import mobilenet_v3_large
from torchvision import transforms
from PIL import Image

class CatanEnvironmentNet(nn.Module):
    def __init__(self, num_terrains=6, num_tokens=13):
        super().__init__()
        backbone = mobilenet_v3_large(weights=None)
        self.features = backbone.features
        self.pool = backbone.avgpool
        feature_dim = 960
        self.terrain_head = nn.Sequential(
            nn.Flatten(), nn.Linear(feature_dim, 128), nn.Hardswish(), nn.Dropout(0.2), nn.Linear(128, num_terrains)
        )
        self.token_head = nn.Sequential(
            nn.Flatten(), nn.Linear(feature_dim, 256), nn.Hardswish(), nn.Dropout(0.2), nn.Linear(256, num_tokens)
        )

    def forward(self, x):
        x = self.features(x)
        x = self.pool(x)
        return self.terrain_head(x), self.token_head(x)

weights_path = hf_hub_download(repo_id="nithinmanoj10/CataNET-Terrain-v1.0-Large", filename="best.pth")
model = CatanEnvironmentNet()
model.load_state_dict(torch.load(weights_path, map_location="cpu"))

# IMPORTANT: see Limitations -- .train() (not .eval()), only Dropout forced to eval,
# and ALWAYS run on a real batch (not a single image) or BatchNorm output collapses.
model.train()
for m in model.modules():
    if isinstance(m, nn.Dropout):
        m.eval()

preprocess = transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize(
        mean=[0.7209611535072327, 0.5950123071670532, 0.4485519230365753],
        std=[0.2021966278553009, 0.20071130990982056, 0.19387811422348022],
    ),
])

TERRAIN_CLASSES = ["desert", "fields", "forest", "hills", "mountains", "pasture"]
TOKEN_CLASSES = ["none", "robber", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"]

patches = [preprocess(Image.open(p).convert("RGB")) for p in ["hex_0.jpg", "hex_1.jpg", "..."]]  # a real batch, not one image
batch = torch.stack(patches)

with torch.no_grad():
    terrain_logits, token_logits = model(batch)
    terrains = [TERRAIN_CLASSES[i] for i in terrain_logits.argmax(1).tolist()]
    tokens = [TOKEN_CLASSES[i] for i in token_logits.argmax(1).tolist()]

Citation

No formal paper accompanies this model. If referencing it, please cite the repository directly:

@misc{catanet-terrain-large,
  author = {nithinmanoj10},
  title = {CataNET-Terrain-v1.0-Large},
  year = {2026},
  publisher = {Hugging Face},
  howpublished = {\url{https://huggingface.co/nithinmanoj10/CataNET-Terrain-v1.0-Large}}
}

Model Card Contact

nithinmanoj10 via Hugging Face.

Downloads last month
23
Inference Providers NEW
This model isn't deployed by any Inference Provider. πŸ™‹ Ask for provider support

Model tree for nithinmanoj10/CataNET-Terrain-v1.0-Large

Quantized
(1)
this model

Dataset used to train nithinmanoj10/CataNET-Terrain-v1.0-Large

Collection including nithinmanoj10/CataNET-Terrain-v1.0-Large

Evaluation results