Skip to content

ClassificationΒΆ

Open in Colab Β· Download notebook

Classification maps one point cloud to one label: a chair, a table, a plane. Models read a packed batch \((N, C)\) and return logits \((B, \text{num\_classes})\), one row per cloud.

Sample objects with predicted classes

Run a pretrained checkpointΒΆ

The example reads one object of the ModelNet40 test set, which ModelNetNormalResampled downloads on first use.

Pass return_info=True to also get the transform registered with the checkpoint, and hand it to the dataset so every sample is ready for the forward pass.

import torch

import torch_pointcloud as tp
from torch_pointcloud.datasets import ModelNetNormalResampled
from torch_pointcloud.utils.data import collate

# Load the pretrained model
model, info = tp.create_model(
    "pointnet2-ssg.modelnet40.xu-yan",
    task="classification",
    pretrained=True,
    return_info=True,
)
model = model.eval()

# Get associated transform
transform = info["transform"]

# Load one object of the ModelNet40 test set
dataset = ModelNetNormalResampled(root="data", variant="40", train=False, download=True, transform=transform)
sample = dataset[0]

# Collate the sample into a batch
data = collate([sample])

# Inference pass
with torch.no_grad():
    logits = model(data.get("x"), data["pos"], data["batch"])  # (1, 40)

# Get predicted classes
classes = info["weights"]["classes"]
top = logits.softmax(dim=-1).topk(3, dim=-1)
for index, score in zip(top.indices[0].tolist(), top.values[0].tolist()):
    print(f"{classes[index]:>12}  {score:.2f}")

Use data.get('x') rather than data['x']

Checkpoints trained on coordinates only never build an x key, and the models accept None.

Inputs and outputsΒΆ

Argument Shape Meaning
x \((N, C)\) or None Per-point features, assembled by the checkpoint's transform (normals, height, color)
pos \((N, 3)\) Coordinates, all clouds in the batch concatenated
batch \((N,)\) Per-point cloud index, \(0 \ldots B-1\)
returns \((B, \text{num\_classes})\) Class logits, one row per cloud

Evaluate on a datasetΒΆ

The same transform slots into a dataset, so reproducing a published score is a plain loop. ModelNetNormalResampled is the presampled ModelNet40 release the published numbers use; it downloads on first use and takes a few GB on disk.

import torch
from tqdm.auto import tqdm

import torch_pointcloud as tp
from torch_pointcloud.datasets import ModelNetNormalResampled
from torch_pointcloud.utils.data import PointCloudDataLoader
from torch_pointcloud.config import DATA_DIR

model, info = tp.create_model(
    "pointnet2-ssg.modelnet40.xu-yan",
    task="classification",
    pretrained=True,
    return_info=True,
)
model = model.cuda().eval()

dataset = ModelNetNormalResampled(
    root=DATA_DIR,
    variant="40", 
    train=False,
    transform=info["transform"],
)
dataloader = PointCloudDataLoader(dataset, batch_size=32, num_workers=6)

correct = total = 0
with torch.no_grad():
    for data in tqdm(dataloader, desc="Evaluating"):
        logits = model(None, data["pos"].cuda(), data["batch"].cuda())
        correct += (logits.argmax(dim=-1).cpu() == data["label"]).sum().item()
        total += data["label"].numel()

print(f"overall accuracy: {correct / total:.4f}")
# Evaluating: 100%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ| 78/78 [00:24<00:00,  3.24it/s]
# overall accuracy: 0.9230

Train from scratchΒΆ

Build an untrained model by name and pass the data-dependent arguments. in_channels counts the columns of x (0 when the model reads coordinates only).

import torch
import torch_pointcloud as tp

model = tp.create_model(
    "pointnet2-ssg.modelnet40.xu-yan",
    task="classification",
    in_channels=0,
    num_classes=12,
)

pos = torch.randn(4096, 3)
batch = torch.arange(4).repeat_interleave(1024)
logits = model(None, pos, batch)

print(tuple(logits.shape))
# (4, 12)

Fine-tune a pretrained backboneΒΆ

You can fine-tune a pretrained backbone by overriding num_classes while loading pretrained weights. This will load the backbone and rebuild the head with the desired number of classes.

model = tp.create_model(
    "pointnet2-ssg.modelnet40.xu-yan",
    task="classification",
    pretrained=True,
    num_classes=10,
)

# OR
model.reset_classifier(10)

CheckpointsΒΆ

858590909595100100Β±1 pointReference scoreMeasured score
CheckpointParams (M)Ref.MeasuredΞ”License
ModelNet40 OA
pointgpt-b.modelnet40.guangyan-chen120.494.4 94.37βˆ’0.03MIT
pointgpt-b.modelnet40-8k.guangyan-chen120.4–94.25MIT
pointnext-sm-c64.modelnet40.openpoints4.594.0 93.96βˆ’0.04MIT
pointgpt-l.modelnet40-8k.guangyan-chen360.4–93.92MIT
pointgpt-l.modelnet40.guangyan-chen360.494.7 93.88βˆ’0.82MIT
pointmlp-base.modelnet40.xu-ma13.294.1 93.88βˆ’0.22Apache 2.0
pointgpt-s.modelnet40-8k.guangyan-chen29.294.2 93.72βˆ’0.48MIT
point-mamba-base.modelnet40.dingkang-liang12.393.6 93.64+0.04Apache 2.0
dgcnn.modelnet40-2048.an-tao1.893.6 93.600.00MIT
point-mae-base.modelnet40-8k.yatian-pang22.194.0493.35βˆ’0.69MIT
pointgpt-s.modelnet40.guangyan-chen29.294.0 93.31βˆ’0.69MIT
dgcnn.modelnet40-1024.an-tao1.893.3 93.27βˆ’0.03MIT
point-bert-base.modelnet40-8k.xumin-yu22.193.1993.07βˆ’0.12MIT
point-bert-base.modelnet40-4k.xumin-yu22.192.9192.99+0.08MIT
point-m2ae-base.modelnet40.renrui-zhang12.893.4392.87βˆ’0.56MIT
point-mae-base.modelnet40.yatian-pang22.193.1992.87βˆ’0.32MIT
pointmlp-elite.modelnet40.xu-ma0.7293.6 92.79βˆ’0.81Apache 2.0
pointnet2-msg.modelnet40.xu-yan1.892.8 92.67βˆ’0.13MIT
point-bert-base.modelnet40.xumin-yu22.192.6792.63βˆ’0.04MIT
pointconv-density-base.modelnet40.wenxuan-wu19.692.5 92.30βˆ’0.20MIT
pointnet2-ssg.modelnet40.xu-yan1.592.0 92.30+0.30MIT
octformer-base.modelnet40.octree-nn4.092.7 92.02βˆ’0.68MIT
pointnet2.modelnet40.openpoints1.593.0 91.90βˆ’1.10MIT
ScanObjectNN PB_T50_RS OA
pointgpt-l.scanobjectnn-hardest.guangyan-chen360.493.4 93.75+0.35MIT
pointgpt-b.scanobjectnn-hardest.guangyan-chen120.491.9 91.92+0.02MIT
point-mamba-base.scanobjectnn-hardest.dingkang-liang12.389.3189.28βˆ’0.03Apache 2.0
pointnext-sm.scanobjectnn-hardest.openpoints1.488.2088.17βˆ’0.03MIT
pointgpt-s.scanobjectnn-hardest.guangyan-chen29.286.9 86.95+0.05MIT
point-m2ae-base.scanobjectnn-hardest.renrui-zhang12.986.4386.54+0.11MIT
pointnet2.scanobjectnn-hardest.openpoints1.586.2 86.16βˆ’0.04MIT
pointmlp-base.scanobjectnn-hardest.xu-ma13.286.1 85.81βˆ’0.29Apache 2.0
point-mae-base.scanobjectnn-hardest.yatian-pang22.185.1885.05βˆ’0.13MIT
pointmlp-elite.scanobjectnn-hardest.xu-ma0.7184.1 84.18+0.08Apache 2.0
point-bert-base.scanobjectnn-hardest.xumin-yu22.183.0783.070.00MIT
ScanObjectNN OBJ_BG OA
pointgpt-l.scanobjectnn-objbg.guangyan-chen360.497.2 98.45+1.25MIT
pointgpt-b.scanobjectnn-objbg.guangyan-chen120.495.8 97.07+1.27MIT
point-mamba-base.scanobjectnn-objbg.dingkang-liang12.394.3294.320.00Apache 2.0
pointgpt-s.scanobjectnn-objbg.guangyan-chen29.291.6 91.57βˆ’0.03MIT
point-m2ae-base.scanobjectnn-objbg.renrui-zhang12.991.2291.220.00MIT
point-mae-base.scanobjectnn-objbg.yatian-pang22.190.0290.19+0.17MIT
point-bert-base.scanobjectnn-objbg.xumin-yu22.187.4387.44+0.01MIT
ScanObjectNN OBJ_ONLY OA
pointgpt-l.scanobjectnn-objonly.guangyan-chen360.496.6 96.90+0.30MIT
pointgpt-b.scanobjectnn-objonly.guangyan-chen120.495.2 95.18βˆ’0.02MIT
point-mamba-base.scanobjectnn-objonly.dingkang-liang12.392.6092.600.00Apache 2.0
pointgpt-s.scanobjectnn-objonly.guangyan-chen29.290.0 90.71+0.71MIT
point-mae-base.scanobjectnn-objonly.yatian-pang22.188.2988.30+0.01MIT
point-bert-base.scanobjectnn-objonly.xumin-yu22.188.1288.120.00MIT