spot-wetfloor-convnext-tiny
Binary image classifier: wet (visible standing water or a wet surface) vs dry.
Fine-tuned from facebook/convnext-tiny-224 (Apache-2.0).
Built for SPOT Dispatch, a readiness board for Boston Dynamics Spot inspection routes. Spot's user guide says not to walk on wet or slippery surfaces, so the board can flag route photos that look wet for an operator to review.
Intended use
- Advisory flag only. It marks a route image as "possibly wet" so a human operator checks the route before a mission.
- "dry" never means safe. A
dryprediction means only that the model did not see water. Thin films, oil, ice, glare, dark areas, and anything outside the frame are not detected reliably. The operator always confirms. - Not for autonomous go/no-go decisions, and not a certified safety system.
How to use
from transformers import pipeline
clf = pipeline("image-classification", model="builtbyd3v/spot-wetfloor-convnext-tiny")
clf("floor.jpg") # [{'label': 'wet', 'score': ...}, {'label': 'dry', 'score': ...}]
transformers.js (ONNX weights in onnx/model.onnx, fp32):
import { pipeline } from "@huggingface/transformers";
const clf = await pipeline("image-classification", "builtbyd3v/spot-wetfloor-convnext-tiny", { dtype: "fp32" });
await clf("floor.jpg");
Quantized ONNX
onnx/model_quantized.onnx (33.8 MB vs 111.4 MB fp32) loads with { dtype: "q8" }. It is onnxruntime quantize_dynamic, uint8 weights, MatMul/Gemm layers only (convs stay fp32). Scored with onnxruntime on the same test split:
| Weights | Accuracy (752) | Wet recall (752) | Same-source accuracy (469) | Same-source wet recall (41) |
|---|---|---|---|---|
| fp32 | 99.6% | 99.1% (321/324) | 99.4% | 92.7% (38/41) |
| q8 | 99.5% | 98.8% (320/324) | 99.1% | 90.2% (37/41) |
q8 misses one more same-source wet image and changes 1 of 752 test predictions vs fp32 (kit images: same calls). Use fp32 when recall matters more than load time. Quantizing the convs too (28 MB) lost 2 same-source wet images, so it is not shipped.
Results (held-out test set, never used for training or model selection)
Test set: 752 images (428 dry, 324 wet). 95% CIs are Wilson score intervals per image.
| Model | Accuracy | Wet recall (safety number) | Dry recall |
|---|---|---|---|
Majority class (always dry) | 56.9% (53.3-60.4) | 0.0% (0.0-1.2) | 100.0% (99.1-100.0) |
| CLIP ViT-B/32 zero-shot | 71.8% (68.5-74.9) | 72.2% (67.1-76.8) | 71.5% (67.0-75.6) |
| This model | 99.6% (98.8-99.9) | 99.1% (97.3-99.7) | 100.0% (99.1-100.0) |
Same-source subset (the honest number). All dry images come from one dataset and most wet images from another, so the full-test score can partly reflect which dataset an image is from. On the Kaggle-only subset (469 images, 41 wet, same cameras and scenes for both classes):
| Model | Accuracy | Wet recall | Dry recall |
|---|---|---|---|
| Majority class | 91.3% (88.3-93.5) | 0.0% (0.0-8.6) | 100.0% (99.1-100.0) |
| CLIP zero-shot | 72.9% (68.7-76.7) | 87.8% (74.5-94.7) | 71.5% (67.0-75.6) |
| This model | 99.4% (98.1-99.8) | 92.7% (80.6-97.5) | 100.0% (99.1-100.0) |
The 41 same-source wet test images come from only 2 scene groups, so the real uncertainty on that wet recall is wider than the per-image CI shows.
CLIP prompts: "a photo of standing water or a wet floor" vs "a photo of a dry floor". Threshold for this model: p(wet) > 0.5.
Domain gap (read this)
- Training data is public outdoor scenes (sidewalk and road video stills, phone photos of puddles and wet ground). It contains no power-plant floors and no Spot camera frames. It is untested on real Spot images.
- Sanity check on 6 synthetic plant inspection images (all dry): 4 predicted
dry, 2 predictedwet(a pressure gauge close-up at p=0.51, and a thermal pump-bearing image at p=0.63). CLIP called all 6 dry. Expect false alarms on industrial close-ups and thermal images; use it on floor/route views only.
Training data and licenses
| Source | License | Used as | Images |
|---|---|---|---|
| Bhutad, S. and Patil, K. (2021). Dataset of Stagnant Water and Wet Surface with Annotations. Mendeley Data, V4. doi:10.17632/y6zyrnxbfm.4 | CC BY 4.0 | all annotated images (classes water, wet surface) as wet; the unannotated "Raw data" folder skipped | 1,976 |
| Mahesa, M. R. Obstacles in Public Spaces for Dist-YOLO. Kaggle: muftirestumahesa/obstacles-in-public-spaces-for-dist-yolo | CC0 1.0 | images with a puddle box as wet, all others as dry | 3,355 |
Changes made to the Mendeley data (CC BY 4.0 requires saying so): relabeled both classes to one wet class, dropped bounding boxes, resized and cropped for training. These weights are released under CC BY 4.0 to keep the attribution chain clear; the base model is Apache-2.0.
Training
- Split: StratifiedGroupKFold (7 folds, seed 0) stratified on source+label. Groups are runs of consecutive frames (40-frame blocks for Kaggle video stills, 20-image blocks per folder for Mendeley phone bursts), so near-duplicate frames do not cross splits. Train 3,828 / val 751 / test 752.
- Recipe: transformers
Trainerimage classification. 4 epochs, batch 32, lr 5e-5, 10% warmup, weight decay 0.05, best epoch by val accuracy. Light augmentation: random crop 60-100% area, horizontal flip, +-20% brightness/contrast/saturation. - Hardware: Apple M4 Pro, PyTorch MPS, 6.5 minutes wall time.
- ONNX:
torch.onnx.exportopset 17, max abs logit difference vs PyTorch 5e-7. - Training scripts:
training/in this repo.
Known limits
- Label noise: Kaggle images without a
puddlebox can still show wet ground; these count asdry. - Split groups are filename-based; frames at block edges can still be near neighbors.
- Daylight outdoor scenes dominate. Indoor floors, night, and IR are barely covered.