Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

TLNet

A deep learning framework for individual tree detection in forest point clouds using multi-layered forest structure.

TLNet slices a point cloud into horizontal slabs along the height axis, encodes each slab with a sparse convolutional backbone, aggregates the voxel features onto a shared 2D grid, fuses the layers with bidirectional layer-wise attention, and regresses stem positions as a CenterNet-style heat-map plus sub-cell offsets.

Method and results are described in the paper:

Yiliu Tan, Xin Yang, Jingyi Zhang, Xin Xu, Yunjian Cao, Di Wang, Maiko Shigeno. TLNet: A deep learning framework for tree detection in forest point clouds using multi-layered forest structure. ISPRS Journal of Photogrammetry and Remote Sensing, Volume 234, 2026, pp. 227–241. doi:10.1016/j.isprsjprs.2026.02.002


Scope

This repository contains TLNet itself — the model, the loss, the training loop and the evaluation — plus a description of the data it consumes and produces.

It does not prescribe a dataset. You supply a Dataset returning the documented dictionary; TLNet returns tree coordinates and scores. Preprocessing and any downstream use of the detections are left to you.

    your data  ──►  your Dataset  ──►  ┌────────┐  ──►  (xy, scores)  ──►  your analysis
  (any format)     (input contract)    │ TLNet  │      (output contract)
                                       └────────┘

Repository layout

TLNet/
├── configs/tlnet.yaml             All hyper-parameters live here
├── examples/
│   ├── example_dataset.py         Reference Dataset implementation
│   └── sample_grid_gt_data.csv    Ground-truth file format example
├── tlnet/
│   ├── config.py                  YAML loading + command-line overrides
│   ├── data/__init__.py           Input contract, slab slicing, collate
│   ├── models/                    Model, attention, head, decoding, backbone
│   ├── losses/centernet.py        Heat-map focal loss + offset L1
│   ├── engine/                    Training loop and evaluation
│   └── utils/misc.py              Seeding, dataset loading, matching metrics
├── tools/{train,evaluate}.py      Entry points
├── tests/                         Regression tests (no dataset required)
└── docs/SETUP.md                  Environment setup

Requirements

TLNet needs three extensions that must match your PyTorch and CUDA versions: spconv, torch-scatter and torch-cluster. docs/SETUP.md gives a verified recipe and covers the common installation pitfalls.

pip install torch --index-url https://download.pytorch.org/whl/cu117
pip install spconv-cu117

# Install the sparse/graph operators from prebuilt wheels, never from source
pip install torch-scatter==2.1.1 torch-cluster==1.6.1 --only-binary=:all: \
    -f https://data.pyg.org/whl/torch-1.13.1+cu117.html

pip install -r requirements.txt

Verified combination: Python 3.8, PyTorch 1.13.1+cu117, spconv-cu117, torch-scatter 2.1.1, torch-cluster 1.6.1.

The scripts under tools/ add the repository root to sys.path, so pip install -e . is optional.


Input format

Write a torch.utils.data.Dataset whose __getitem__ returns:

Key Type / shape Meaning
points FloatTensor [N, 3] the plot's point cloud (x, y, z). Mutually exclusive with slabs
slabs list of dict pre-sliced layers, bottom to top, each {"points": [N_i, 3]}. Only if you slice the cloud yourself
grid_coords FloatTensor [PQ, 2] centre (x, y) of every grid cell; must form a regular lattice
grid_size float cell edge length
tree_loc_full FloatTensor [PQ, 2] true stem position for each cell, nan where the cell has no tree
tree_grid_idx LongTensor [M] indices into grid_coords of the cells that contain a tree
tree_locs FloatTensor [M, 2] stem positions of those M trees, used by the detection metrics
name str sample identifier, used in logs

Supply either points or slabs. With points, TLNet slices the cloud using data.num_slabs and data.skip_bottom_slabs.

A working implementation is in examples/example_dataset.py.

Ground truth

One row per grid cell, holding the stem position of the tree inside that cell (if any). See examples/sample_grid_gt_data.csv, which opens directly in Excel:

Grid_X,Grid_Y,Tree_X,Tree_Y,treeID
-120.8,-79.3,,,
-118.8,-77.3,-118.24,-77.05,1
-116.8,-77.3,,,
-114.8,-75.3,-114.31,-75.62,2
Column Meaning Required
Grid_X, Grid_Y grid-cell centre
Tree_X, Tree_Y stem position inside the cell; empty if no tree
treeID tree identifier optional
  • Grid_X / Grid_Y must be evenly spaced; the cell size and grid extent are inferred from their unique values.
  • At most one tree per cell.

Units

All coordinates must share one unit, and every length in the configuration (model.voxel_size, model.radius, eval.match_radius) uses that same unit. data.units_per_meter only says how to convert distances for reporting: set it to 10 for decimetres, 1 for metres, 100 for centimetres.

The shipped configuration assumes decimetres. If your data is in metres, either multiply the coordinates by 10, or set units_per_meter: 1 and divide voxel_size, radius and match_radius by 10.


Output format

tlnet.engine.predict(model, batch, device, cfg_eval) returns (pred_xy, scores, outputs):

Value Type / shape Meaning
pred_xy FloatTensor [K, 2] absolute stem coordinates, in the same unit as the input
scores FloatTensor [K] heat-map score of each detection, in (0, 1)
outputs dict the raw detection-head output, see below

K varies per plot: detections are the cells that survive NMS and exceed eval.score_thresh. Positions already include the regressed sub-cell offset, so they are not snapped to cell centres.

If you want the dense maps instead of the decoded list, outputs contains:

Key Type / shape Meaning
hm FloatTensor [1, 1, H, W] heat-map in (0, 1), before NMS
offset FloatTensor [1, 2, H, W] (dx, dy) per cell, in units of one grid cell
H, W int grid dimensions
grid_size float cell edge length
grid_origin FloatTensor [2] absolute coordinate of cell (0, 0)
grid_coords FloatTensor [PQ, 2] the input grid, passed through

The absolute position of cell (ix, iy) is grid_origin + (ix, iy) * grid_size + offset[:, iy, ix] * grid_size.

NMS and thresholding happen only at decoding time and are detached, so the loss always sees the raw heat-map.


Usage

All hyper-parameters live in configs/tlnet.yaml, which documents each key inline. Point data.dataset at your Dataset class, then:

# Train
python tools/train.py --config configs/tlnet.yaml

# Override any key (dotted path)
python tools/train.py --config configs/tlnet.yaml \
    --opt train.epochs=50 model.radius=10.0

# Evaluate
python tools/evaluate.py --config configs/tlnet.yaml \
    --checkpoint runs/baseline/checkpoints/best.pth

# Evaluate without NMS
python tools/evaluate.py --config configs/tlnet.yaml --checkpoint X \
    --opt eval.nms.enabled=False

data.dataset is an import path "module:ClassName". The class is constructed twice, with split="train" and split="test", receiving data.dataset_args as keyword arguments. If your Dataset has a different signature, build the two objects yourself and call tlnet.data.build_loaders directly.

Using TLNet as a library, without the provided training loop:

from tlnet.data import collate_sample
from tlnet.engine import predict
from tlnet.models import build_model

batch = collate_sample([my_dataset[0]], num_slabs=32, skip_bottom_slabs=2)
pred_xy, scores, outputs = predict(model, batch, device, cfg.eval)

Each run writes the effective configuration to <ckpt_dir>/config_used.yaml. Checkpoints hold the full training state; best.pth is selected by train.best_metric. Evaluation writes eval_per_sample.csv and eval_summary.csv (precision, recall, F1, MAE in metres) to output.eval_dir.


Tests

pytest tests/

Covers grid discretisation, loss target rendering, decoding and NMS, attention trainability and RNG isolation, the architecture switches, and the input contract. Synthetic tensors only, so no dataset is required.


Citation

@article{tan2026tlnet,
  title    = {TLNet: A deep learning framework for tree detection in forest point
              clouds using multi-layered forest structure},
  author   = {Tan, Yiliu and Yang, Xin and Zhang, Jingyi and Xu, Xin and
              Cao, Yunjian and Wang, Di and Shigeno, Maiko},
  journal  = {ISPRS Journal of Photogrammetry and Remote Sensing},
  volume   = {234},
  pages    = {227--241},
  year     = {2026},
  issn     = {0924-2716},
  doi      = {10.1016/j.isprsjprs.2026.02.002},
  url      = {https://www.sciencedirect.com/science/article/pii/S0924271626000535}
}

License

MIT License

About

TLNet: a deep learning framework for tree detection in forest point clouds using multi-layered forest structure

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages