You need to agree to share your contact information to access this model

This repository is publicly accessible, but you have to accept the conditions to access its files and content.

These weights are a fine-tune of Meta's DINOv3 and are distributed under the DINOv3 License, a copy of which is in this repository as LICENSE. You are accepting Meta's terms, not ours. Read LICENSE before accepting: the points below are a summary and the licence text governs.

Section 1(a) grants you a royalty-free licence to use, reproduce, distribute, copy, create derivative works of and modify the materials, with no limit on commercial use. Section 5(a) says any derivative you make is yours.

The conditions that come with that: a derivative may only be distributed under the same Agreement with a copy attached (§1(b)(i)), published research must acknowledge the materials (§1(b)(ii)), no reverse engineering (§1(b)(iv)), and no military, nuclear, espionage or weapons use, with trade controls applying (§1(b)(v)).

The training data is Lantmäteriet's, all under CC BY 4.0. Attribution travels with anything you build on these weights; the citation block on the model card has the wording.

Log in or Sign Up to review the conditions and access this model content.

TIC Forestry v1

Land cover and canopy height from Swedish aerial photos, at 1 m per pixel.

Give it an aerial photo. For every square metre it returns a land cover class and a canopy height.

Example output

512 m of ground, 2024 imagery. Top left is the input. The other three panels are the output.

It is a DINOv3 ViT-L/16 satellite backbone with a multi-scale dense decoder. We fine-tuned it on Lantmäteriet aerial photos, and took the height it had to learn from airborne laser.

Why it predicts height. Photos are reflown about every two years. Laser is much slower. Sweden's forest laser scan started in 2018 and is still going, and covers about 221,000 km2. Where ground has been scanned twice, the gap is usually six or seven years. This model reads canopy height off each new photo, so you do not have to wait for the next laser pass.

parameters 305,625,963. Backbone 303.39 M, head 2.23 M. 465 tensors, bf16
input 512 x 512 at 0.5 m. 4 bands (R, G, B, NIR), uint8
output 256 x 256 at 1 m. 10-class logits, plus canopy height in metres
cross-county mIoU 0.79
height RMSE against laser 3.6 m
VRAM 1,982 MiB in total while serving, driver-reported. Card size is not the constraint

What it is for

Mapping stands, measuring canopy, counting up land cover over an area, and comparing two flights to see what changed. It works at stand scale, which in Sweden means 1 to 5 hectares.

The class means land use, not tree cover

If you read one thing on this page, read this.

The labels come from Lantmäteriet's Topografi 10 mark layer. That is a land use map. On it, a clearfelled stand is still Barr- och blandskog. The model learned the map.

We checked this against laser over 3,568,378 pixels:

predicted class laser canopy under 2 m
conifer_or_mixed_forest 41.3 %
deciduous_forest 47.8 %
water 98.2 %
arable 96.1 %
open_land 83.1 %

So 41 % of the pixels the model calls conifer have nothing over 2 m growing on them. For a land use map that is correct. For a tree cover map it is wrong.

If you want to know whether there are trees, look at the height layer. If you want to know what kind of land it is, look at the class layer.

What it cannot do

  • Find individual trees. One ViT patch covers 8 m of ground.
  • Tell species apart beyond three groups: conifer or mixed, deciduous, mountain birch.
  • Say why something changed. A fire and a clearfelling look the same to it.
  • Handle imagery unlike its training data. See Limitations.

Architecture

backbone DINOv3 ViT-L/16 sat493m. Last 6 of 24 blocks unfrozen, 78.9 M trainable
decoder tokens from depths 6, 12, 18 and 24, fused and upsampled 32 to 256. Widths 256, 128, 64, 32
heads 10-class logits and a canopy height regressor. Both dense at 1 m
fourth band near-infrared is trained from the start. The patch embedding is 4-channel

Why the input is 0.5 m

The sharpest Swedish imagery is 0.16 m, so 0.5 m looks like a waste. Three reasons it is not.

A chip has to be wider than a stand. At 0.5 m a 512 px chip covers 256 m, which holds a stand and its edges. At 0.16 m the same chip covers 82 m and sits inside one stand.

The backbone was pretrained on 0.6 m satellite imagery. 0.5 m is close to what it already knows.

Sweden also has a 0.40 m product, which covers most of the north. 0.5 m is the only grid both products reach by downsampling. Asking for 0.16 m would mean upsampling half the country.

We resample by area-weighted mean. 0.16 m into 0.5 m is 3.125 to 1, so a pixel on a boundary contributes its overlap.

Classes

code name mark.objekttyp wooded colour
0 unlabelled transparent
1 water Sjö, Hav, Vattendragsyta, Anlagt vatten no #2b5f9e
2 arable Åker, Fruktodling no #e8d58a
3 built Låg/Hög/Sluten bebyggelse, Industri- och handelsbebyggelse, Torg no #b0403a
4 conifer_or_mixed_forest Barr- och blandskog yes #1f6b3a
5 deciduous_forest Lövskog yes #7fb84f
6 mountain_birch Fjällbjörkskog yes #a8c47a
7 open_land Öppen mark no #d9c99a
8 bare_mountain Kalfjäll no #9a9a94
9 glacier Glaciär no #e6f2f7

The palette is fixed, so two maps of different ground can be compared. Our own GeoTIFF output embeds it as a TIFF colour table.

Training data

chips 287,612 over 2,743 cells of 10 km, in 17 counties
class labels Lantmäteriet Topografi 10 mark, 3,494,541 polygons
height target our own 1 m canopy model: highest laser return per square metre, minus the terrain model
imagery Lantmäteriet orthophotos, 0.16 m and 0.40 m, 4-band, 2019 onward
schedule AdamW, layer-wise LR decay, cosine over 147,160 steps, 5 % warmup, EMA 0.999
run 20 epochs of 7,358 batches of 32, about 27 min per epoch. One GPU

Three things we decided, and why.

We did not train on NMD. Naturvårdsverkets Nationella Marktäckedata splits forest more finely. We compared it against our labels over 125 million cells of 10 m. NMD took water and arable from the same Lantmäteriet vectors we use, and those agree 100.0 % and 94.2 %. Its forest classes are modelled, and those agree 82.4 % for conifer or mixed and 80.4 % for deciduous. Training on a model's output teaches you that model's mistakes, so we use NMD to score and not to train.

We kept the edges. Every polygon in Topografi 10 carries its own declared positional uncertainty. For the three forest classes the median is 3.0 m and the 90th percentile 4.5 m, against 0.6 m for water and arable. Lantmäteriet's specification only requires 20 m on a forest boundary, so the delivered data is well inside its own ceiling, but a forest edge is still five times looser than a shoreline. The obvious fix is to shrink every labelled area by 3 m and throw the border away. That also throws away every example of an edge, and finding where one stand ends and the next begins is most of the job. So we draw the classes exactly as the polygons are, and put a second raster next to it saying how far each pixel is from the nearest border.

We kept stale labels. Each chip records how much of its canopy agrees with its class. A low number means the stand was felled after the polygon was drawn. We left those in rather than rewrite the label.

Evaluation

We kept whole counties out of training, with a 10 km margin around them, instead of picking random tiles. Trees are shorter in the north: chips in Norrbotten have a median canopy of 2.1 m, chips in Skåne 5.5 m. With random tiles the model would have seen the ground next door and the score would flatter it.

  • validation: Östergötland, Blekinge
  • test: Örebro, Västerbotten
test val
mIoU 0.8102 (8 classes) 0.8013 (6 classes)
pixel accuracy 0.9141 0.9119
canopy height R² 0.6498 0.5915
height RMSE / MAE 3.64 m / 2.05 m 4.76 m

Those two mIoUs are not comparable. The southern counties have six of the classes. Västerbotten adds mountain birch and bare mountain, and both score better than average, which lifts the test number. Comparing only the six classes both sides have, val is 0.8013 and test is 0.7903.

Use 0.79.

class val test
water 0.975 0.906
bare mountain 0.925
arable 0.901 0.916
built 0.874 0.901
conifer or mixed 0.894 0.894
mountain birch 0.815
open land 0.564 0.580
deciduous 0.599 0.545

Checked against laser

The scores above use the same targets the model trained on. So we also scored the height head against laser, over 14 random 512 m windows and 3,568,378 pixels.

R² against laser 0.647 (test split gave 0.6498)
RMSE 3.65 m (test split gave 3.64 m)
bias, model minus laser +0.33 m

The photo is on average 2.6 years newer than the laser. Trees grow in that gap, which is the likely reason the bias is positive.

Limitations

  • Deciduous and open land are weak. 0.545 and 0.580, against 0.81 to 0.93 for everything else. They fail for different reasons. Deciduous is only 5.0 % of labelled pixels, and 12x rarer than conifer. Open land is 8.8 % of pixels, but only 29.4 % of it forms blocks, so it is nearly all boundary.
  • The class is land use. See above.
  • Forest edges are the loosest part of the labels. Median declared uncertainty is 3.0 m on the forest classes and 0.6 m on water and arable, so a stand boundary is about where the map says and not exactly.
  • 2019 onward only. The Swedish archive has no near-infrared band before 2019, so change analysis starts there.
  • Satellite imagery is untested. The backbone was pretrained on satellite images, but this head only ever saw 0.5 m aerial photos. Sentinel-2 is 20x coarser. We have not tried it, so we do not know how it does.
  • Sweden only. Swedish classes, Swedish laser, Swedish test counties.
  • Put a threshold on your change maps. Not because the model is unstable: the same chip gives bit-identical classes and heights whatever else is in the batch, measured. But pixels near a class boundary flip easily. Move the cut by 128 m and 22.8 % of the overlap changes class, and two implementations of this model agree on 99.87 % of pixels rather than 100 %.

Serving geometry

The answer depends on where you cut the image. Across a tile join, 33.24 % of pixels change class. Away from a join it is 1.50 %. Centre a chip on the join instead and it drops to 3.23 %.

So we serve a 128 m cell cut from the middle of a 256 m chip. Every pixel we answer sat at least 64 m from the edge of what the model saw.

This is not only an edge problem. 74.7 % of all class flips are two pairs: conifer to deciduous (44.9 %) and built to open land (29.8 %). Those are the same pairs the IoU table is worst on.

Running it

config.json has the band order, the normalisation statistics, the class names and the EPSG code. Scale uint8 to 0-1, then normalise with input.mean_0_1 and input.std_0_1.

The architecture is custom (TicForestryDinoV3), so AutoModel will not load it.

What it costs to hold

Read off the driver while the runner was serving, at pass width 8:

MiB
weights (f16 planes, f32 norms) 587
forward workspace 785
CUDA context, kernel modules, allocator slack ~610
total, as nvidia-smi sees it 1,982

Under 2 GB all in, and more than half of that is fixed cost rather than the model. A bigger pass width grows the workspace row and nothing else.

The serving API

We serve this on paddock, our own inference engine. Paddock is open source, dual-licensed MIT or Apache-2.0.

Here is the wire format. It is the one we used to make the reference outputs in fixtures/.

POST /v1/segmentations?response_format=binary
Authorization: Bearer <key>
Content-Type: application/octet-stream

<chip><chip>...

A chip is 512 x 512 x 4 uint8, HWC, interleaved R, G, B, NIR. That is 1,048,576 bytes. Put as many chips in one body as you like. Add &logits=true if you want the raw logits back.

The response holds one block per chip, in the order you sent them:

field bytes layout
classes 65,536 256 x 256 uint8. Argmax over the ten classes, row-major
height 262,144 256 x 256 float32 little-endian, in metres
logits 1,310,720 256 x 256 x 10 float16 little-endian. Only with logits=true

That is 327,680 bytes per chip, or 1,638,400 with logits.

Two things to get right.

The logits come back pixel-major, as [y][x][class]. Our fixtures are class-major. Read the wrong way they still decode, and still look like logits. The maximum error is 34, against 0.77 for the right reading. You will not get an error.

Height is float32 metres, and the head can return negatives. You will see small ones over water and at building edges. That is the model being wrong, not a nodata value.

response_format=json returns the same rasters base64-encoded. It costs about a third more bytes.

Send about 24 chips per request.

Measured 2026-09-20 on paddock, one NVIDIA RTX PRO 6000 Max-Q, over a LAN. One request per row, median of three.

chips time chips/s
1 0.051 s 19.4
4 0.096 s 41.6
12 0.277 s 43.4
24 0.502 s 47.8
48 0.967 s 49.6
96 1.882 s 51.0

One chip per request wastes two thirds of the throughput. Four fixes most of that. Past about 24 the curve is nearly flat, so request size stops being the thing that matters, and larger requests cost you upload and latency: 96 chips is 96 MB and almost two seconds.

Your own numbers will differ with the GPU, the network and whatever else the card is serving.

Reference outputs

fixtures/ holds twelve chips from the test counties. Between them they cover all eight classes those counties contain. Each chip comes two ways: a raw 512 x 512 x 4 uint8 block, and a georeferenced 4-band GeoTIFF in SWEREF 99 TM. Both come with the reference class argmax and height raster. The first three chips also carry full logits.

We ported the model to another runtime and checked it against these. It agreed on 99.87 % of pixels, and three chips matched exactly. Every pixel that differed was a near-tie. The median top-1 margin was 0.0156, against 3.383 across the chip. 53 of the 57 sat on a class boundary.

Compare logits with a tolerance, not for equality.

Licence

These weights ship under the DINOv3 License. A copy is in this repository as LICENSE.

Section 1(a) grants a "non-exclusive, worldwide, non-transferable and royalty-free limited license ... to use, reproduce, distribute, copy, create derivative works of, and make modifications to the DINO Materials". There is no limit on commercial use. Section 5(a) says your derivative is yours.

You may redistribute a fine-tuned version. The conditions:

  • §1(b)(i). A derivative goes out under the same Agreement, with a copy attached. So these weights cannot be relicensed MIT or Apache, and neither can anything built on them.
  • §1(b)(ii). Acknowledge the materials in published research.
  • §1(b)(iv). No reverse engineering.
  • §1(b)(v). No military, nuclear, espionage or weapons use. Trade controls apply.
  • §6 to §8. Meta can terminate on breach, California law applies, and Meta can amend the Agreement.

You do not have to put "DINOv3" in the name.

Training data

All four Lantmäteriet sources are CC BY 4.0. You can use them commercially and build on them. You have to credit them.

source where the licence is stated
Ortofoto Nedladdning declared on all 734 collections of api.lantmateriet.se/stac-bild/v1/
Markhöjdmodell declared in api.lantmateriet.se/stac-hojd/v1/
Laserdata Skog the STAC says other and links its terms. §3.1 of those terms names CC BY 4.0 and allows commercial use
Topografi 10 Nedladdning, vektor Lantmäteriet product documentation

Citation

TIC Forestry v1: land cover and canopy height from Swedish orthophotos.
The Intelligence Company, 2026.
DINOv3 ViT-L/16 sat493m backbone (Meta AI), fine-tuned on Lantmäteriet
orthophotos, Topografi 10 and Laserdata Skog.

Contains data from Lantmäteriet, processed:

  • Ortofoto Nedladdning, © Lantmäteriet, CC BY 4.0
  • Markhöjdmodell, © Lantmäteriet, CC BY 4.0
  • Laserdata Nedladdning, skog, © Lantmäteriet, CC BY 4.0
  • Topografi 10 Nedladdning, vektor, © Lantmäteriet, CC BY 4.0

Built on DINOv3 (Meta AI), used under the DINOv3 License.

Downloads last month
12
Safetensors
Model size
0.3B params
Tensor type
BF16
·
Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support

Model tree for tic-oss/tic-forestry-v1