ULM-DS-Lab/Sawit-Weight
Sawit-Weight Two-view field photographs of oil palm fresh fruit bunches (FFB, tandan buah segar), each paired with a bounding box and the ground-truth weight measured on a scale at the collection point. The dataset targets vision-based weight estimation and bunch detection for smallholder and estate harvest logistics. Ringkasan: 31 tandan buah segar kelapa sawit varietas TANERA dari blok 303, difoto dari dua sisi dan ditimbang langsung di lapangan. Setiap gambar disertai kotak… See the full description on the dataset page: https://huggingface.co/datasets/ULM-DS-Lab/Sawit-Weight.
Sawit-Weight
Two-view field photographs of oil palm fresh fruit bunches (FFB, tandan buah segar), each paired with a bounding box and the ground-truth weight measured on a scale at the collection point. The dataset targets vision-based weight estimation and bunch detection for smallholder and estate harvest logistics.
Ringkasan: 31 tandan buah segar kelapa sawit varietas TANERA dari blok 303, difoto dari dua sisi dan ditimbang langsung di lapangan. Setiap gambar disertai kotak pembatas format YOLO serta berat, tinggi, lingkar, fraksi kematangan, dan berat brondolan.
Contents
Every bunch is photographed twice, from side 1 and side 2. The two boxes of the same bunch are linked through bunch_id, so a model can be trained on single views or on paired views.
Loading
from datasets import load_dataset
ds = load_dataset("ULM-DS-Lab/Sawit-Weight", split="train")
sample = ds[0]
sample["image"] # PIL.Image
sample["objects"]["weight_kg"] # [16.928]
sample["objects"]["bbox_yolo"] # [[cx, cy, w, h]] normalisedPer-tree records, which group both sides of a bunch together, are in annotations/trees.jsonl:
import json
trees = [json.loads(line) for line in open("annotations/trees.jsonl", encoding="utf-8")]Layout
data/train/ 62 JPEG images + metadata.jsonl (one row per image)
annotations/labels/ YOLO .txt labels, one per image (62 files)
annotations/trees.jsonl one row per tree, both sides grouped by bunch_id
annotations/classes.txt class names, one per line
annotations/data.yaml Ultralytics YOLO configuration
annotations/raw/ the as-captured record, untouched by the corrections below
annotations/raw/trees/ original per-tree capture JSON (31 files)
annotations/raw/labels/ original YOLO labels (62 files)
annotations/raw/manifests/ SHA-256 manifest per tree (31 files)
annotations/raw/device_metadata/ capture device and GPS record per tree (31 files)
annotations/raw/capture_set.json session descriptor and per-tree content digests
scripts/verify_dataset.py re-checks every SHA-256 and every annotation invariantFields
data/train/metadata.jsonl — one row per image
objects — parallel lists, one entry per box
annotations/trees.jsonl carries the same measurements grouped per tree, plus total_weight_kg, total_detections, and the appearances of each bunch across sides.
Measurement ranges
Ripeness distribution: fraction 1 (1), 2 (3), 3 (7), 4 (14), 5 (6).
Ripeness follows the Indonesian fraksi scale for fresh fruit bunches, which grades a bunch by how many fruits have detached: 1 is the least ripe, with a full, tightly packed bunch and almost no empty sockets, and 5 is over-ripe, with most fruit shed and the spikelets exposed and drying.
Collection
Bunches were photographed at the collection point immediately after harvest with a phone camera, then weighed on a field scale. Height, circumference, ripeness fraction, and loose fruit weight were recorded by hand on the same form. Bounding boxes were drawn by the field team and linked across the two sides of each tree; one box the team missed was added later from the image and is flagged as such.
Each tree ships with a SHA-256 manifest covering its images, labels, and JSON. The script re-checks those digests and confirms that the corrected layer changes nothing beyond the declared corrections:
python scripts/verify_dataset.py .Corrections applied
The field record had three gaps. Each was resolved by reviewing the photographs against the bunches whose ripeness the field team did record, and every correction is flagged in the data so it can be filtered out. The untouched original is kept under annotations/raw/, where the per-tree SHA-256 manifests still validate.
Reasoning behind the three ripeness calls, for anyone who wants to revisit them:
0003retains far more fruit than the fraction 5 references but shows markedly more exposed spikelets and empty sockets than the fraction 3 references, matching fraction 4.0004is the fullest bunch in the dataset, with tightly packed fruit and almost no empty sockets, but its fruit already carries the red tone of an early-ripe bunch rather than the darker cast of fraction 1. Graded fraction 2 by the data owner.0005is almost entirely shed and drying, visually indistinguishable from the fraction 5 references.
To train only on field-recorded annotations, keep rows where every entry of ripeness_fraction_source is field_note and every entry of bbox_source is field.
Known limitations
Read these before training on the dataset.
- Single site, single variety, single day. All 31 trees are variety TANERA from block 303, captured on 2026-09-14. Models fitted here will not transfer to other varieties, regions, or lighting conditions without further data.
- Small sample. 31 weighed bunches is enough to prototype a pipeline, not to fit a production regressor.
- Three ripeness labels and one box are visual estimates, not field measurements. See the corrections above.
- Ripeness is imbalanced. Fraction 4 holds 14 of 31 bunches while fraction 1 holds a single one, so the dataset will not support a balanced five-class classifier as it stands.
- GPS is absent for 13 trees. Trees
0001-0013were captured with location off. Two further readings (0020,0024) come from the network provider at 300 m accuracy rather than the GNSS receiver; checkgps_accuracy_mbefore using coordinates. - Some measurements look estimated. Five bunches have integer weights (17, 30, 15, 15, 17 kg) and
loose_fruit_kgof 1.3 repeats across five trees, which suggests rounding at entry time rather than scale readings. - One implausible loose fruit value. Tree
0007records 5.624 kg of loose fruit against a 3.2 kg bunch. The value is preserved as recorded but should be treated as suspect. - No operator attribution.
operatorisUNKNOWNfor every tree.
Coordinates
Trees 0014-0031 include GNSS coordinates at 5-15 m accuracy, published at full precision with the consent of the data owner. They identify a specific plantation block in South Kalimantan, Indonesia.
License
Released under CC BY-NC 4.0. Attribution required; commercial use is not permitted.
Citation
@misc{ulm_ds_lab_sawit_weight,
title = {Sawit-Weight: Two-View Oil Palm Fresh Fruit Bunch Images with Measured Weights},
author = {{ULM Data Science Lab}},
year = {2026},
url = {https://huggingface.co/datasets/ULM-DS-Lab/Sawit-Weight}
}