Aspirin4/synthetic-glass-with-liquid-filled
๐ฅ Glass Half Full โ Synthetic Glass with Liquid Filled 8,000 synthetic images of drinking glasses with varying liquid fill levels, rendered with Blender Cycles (physically-based path tracer) at 256ร256 resolution. Every image ships with perfect YOLO-format bounding-box labels for two classes โ glass and liquid โ computed directly from 3D geometry (no human annotation). Built for the Existential Glass Analyzer, a browser-based model that answers the timeless question: is yourโฆ See the full description on the dataset page: https://huggingface.co/datasets/Aspirin4/synthetic-glass-with-liquid-filled.
๐ฅ Glass Half Full โ Synthetic Glass with Liquid Filled
8,000 synthetic images of drinking glasses with varying liquid fill levels, rendered with Blender Cycles (physically-based path tracer) at 256ร256 resolution. Every image ships with perfect YOLO-format bounding-box labels for two classes โ glass and liquid โ computed directly from 3D geometry (no human annotation).
Built for the Existential Glass Analyzer, a browser-based model that answers the timeless question: is your glass half full or half empty?
Table of contents
- Quick start
- Why synthetic?
- Data formats
- Parquet (recommended)
- YOLO layout
- Raw YOLO labels
- Schema reference
- Splits
- Fill ratio
- Generation pipeline
- Labeling methodology
- Dataset quality
- Limitations
- Changelog
- Attribution
Quick start
from datasets import load_dataset
ds = load_dataset("Aspirin4/synthetic-glass-with-liquid-filled")
print(ds["train"][0]["objects"]) # bbox, category, area, iscrowd
print(ds["train"][0]["fill_ratio"])
# PyTorch + transforms if you need a dataloader
ds.set_format("torch")YOLO (Ultralytics):
yolo detect train data=data.yaml model=yolov8n.pt epochs=100 imgsz=256Why synthetic?
No public dataset exists for "glass fill level detection." Manual labeling of transparent liquids is slow, subjective, and inconsistent โ where does the liquid really end when you can see through the glass?
Synthetic data solves this cleanly:
- Mathematically exact bounding boxes โ labels are projected from 3D geometry, so they are pixel-perfect by construction (zero annotation cost, zero human error).
- Full coverage of the fill range โ fill levels are sampled uniformly, so the model sees the entire 0โ100% continuum.
- Controlled domain randomization โ lighting, camera, liquid color, ice, and environment are varied systematically, which is exactly what a downstream real-world model needs to generalize.
Data formats
The same 8,000 images are provided in three formats so the dataset works with any toolchain without conversion.
1. Parquet (recommended)
One row per image, COCO-style annotations, images referenced by path. These files are what power the dataset viewer on this page and what load_dataset reads:
train-00000-of-00001.parquetval-00000-of-00001.parquettest-00000-of-00001.parquet
from datasets import load_dataset
ds = load_dataset("Aspirin4/synthetic-glass-with-liquid-filled")
row = ds["train"][0]
row["image"] # PIL image (decoded from images/glass_XXXXX.png)
row["image_id"] # 4747
row["fill_ratio"] # 0.179 (liquid height / glass height)
row["objects"]["bbox"] # [[x_min, y_min, w, h], ...] in pixels
row["objects"]["category"] # ['glass', 'liquid']
row["objects"]["category_id"] # [0, 1]
row["objects"]["area"] # [13690.85, 1768.26] pxยฒ
row["objects"]["iscrowd"] # [0, 0]2. YOLO layout
Ready for Ultralytics / Darknet training out of the box. Split membership is defined by index files (one image path per line) rather than duplicated image folders, so images/ stays the single source of truth:
train.txt # 6,394 image paths
val.txt # 791 image paths
test.txt # 815 image paths
data.yamldata.yaml points at the index files and declares the class names:
path: . # repo root (adjust if you clone elsewhere)
train: train.txt
val: val.txt
test: test.txt
nc: 2
names: ['glass', 'liquid']Train with Ultralytics:
yolo detect train data=data.yaml model=yolov8n.pt epochs=100 imgsz=2563. Raw YOLO labels
The canonical source of truth โ one .txt per image in labels/:
# labels/glass_00000.txt
0 0.496711 0.489107 0.409185 0.726577 # glass: class cx cy w h (normalized)
1 0.498119 0.616097 0.292432 0.434514 # liquid: class cx cy w h (normalized)images/glass_00000.png โ labels/glass_00000.txt. All coordinates are normalized to [0, 1] relative to image width/height (YOLO convention).
Schema reference
Parquet columns
Note on bbox convention: parquet uses COCO pixel coordinates ([x_min, y_min, w, h]); YOLO.txtfiles use normalized center form (cx, cy, w, h). To convert:x_min = (cx - w/2) * 256,y_min = (cy - h/2) * 256,w_px = w * 256,h_px = h * 256.
Class map
Splits
Stratified 80 / 10 / 10 split by fill_ratio (seed 42). Stratification uses 20 quantile buckets of fill ratio so every split covers the full range of fill levels โ a naive random split would leave thin slices of the fill distribution only in one split.
Split assignment is deterministic (seeded), so train.txt from the YOLO layout and train from the parquet contain the same images.
Fill ratio
The fill_ratio column is the core regression target for this dataset and is computed as:
fill_ratio = liquid_bbox_height / glass_bbox_heightBecause both bounding boxes are projected from the same 3D geometry under the same camera, perspective foreshortening cancels out โ the ratio is a robust proxy for the true liquid level in the glass, largely independent of camera elevation and distance.
Decile histogram of fill ratio across the full dataset:
The distribution is approximately uniform across the middle of the range with fewer samples at the extremes (very empty / very full), matching the uniform sampling of liquid_fill_ratio โ [0.02, 0.98] in the generator combined with the projection step.
Generation pipeline
Generated with GlassHalfFull/scripts/blender/generate.py (Blender + Cycles):
liquid_fill_ratio = uniform(0.02, 0.98) # sample fill level
liquid_height = liquid_fill_ratio * GLASS_HEIGHT
โ randomize scene (camera, lights, material, HDRI, ice, grain)
โ render 256ร256 (Cycles path tracer)
โ project glass + liquid 3D bboxes โ YOLO labelDomain randomization per frame
The goal is systematic variation with exact labels โ everything that makes real-world glass detection hard (reflections, transparency, specular highlights, ice occlusion, colored liquids) is varied, while the ground truth stays mathematically exact.
Labeling methodology
Bounding boxes are not hand-annotated. The generator:
- Takes the 8 corners of the glass object's world-space bounding box and the 8 corners of the liquid cylinder (
radius = LIQUID_RADIUS,z = 0 โ liquid_height). - Projects them to camera space with
world_to_camera_view. - Flips Y (Blender camera has Y-down; YOLO has Y-up).
- Clamps to
[0, 1], drops boxes smaller than 0.001.
Because the liquid is a perfect cylinder of known height and the glass is a known primitive, the resulting boxes are exact for the rendered content โ no annotator subjectivity, no edge-case disagreements.
Dataset quality
Verified programmatically:
- โ All 8,000 images have exactly two objects (one glass, one liquid)
- โ
All coordinates in
[0, 1]โ zero out-of-bounds boxes - โ
Class ids strictly
{0, 1} - โ Every image has a matching label file (no orphans either way)
- โ Image dimensions uniform (256ร256)
- โ Fill ratio continuous across 0.14 โ 0.96
Limitations
- Synthetic-to-real gap. Models trained on this dataset may overfit to Cycles lighting. The gap is most visible in specular highlights and shadow softness. For real-world deployment, fine-tune on a small set of real photos.
- Single glass geometry. All renders use one glass model โ a model trained here may not generalize to shot glasses, tumblers, pint glasses, or bottles.
- Resolution. 256ร256 is modest; fine details (thin liquids, small ice) are coarse.
- No negative examples. Every image contains exactly one glass โ no empty scenes, no multiple glasses, no occluders other than ice.
Changelog
- v1.1 (2026-08-23)
- Removed 9 non-dataset images (
glass_09000โglass_09008, demo images from the project website) that had been accidentally included โ exactly 8,000 images. - Added parquet splits (
train/val/test) with COCO-style annotations and derivedfill_ratio. - Added YOLO split layout (
train.txt,val.txt,test.txt,data.yaml). - Rewrote dataset card.
- v1.0 โ original release: 8,009 images + labels as zips.
Attribution
Textures used during rendering:
- Wood floor: OnAirDesign Dark Wood Texture Board
- Sky background: Pinterest
License
Apache 2.0. See LICENSE for terms. You are free to use, modify, and redistribute with attribution.
