
Colour jitter and small random crops help manipulation policies. Flips, rotations and large shifts can break them, because the action label is tied to the image geometry.
In image classification, augmentation is nearly free. A cat rotated by fifteen degrees is still a cat, so you flip, rotate, crop and jitter, and the label survives. Manipulation policies are not built that way. The label is an action, usually joint targets or an end effector delta, and it lives in the robot's frame. The image lives in the camera's frame. Augmentation assumes those frames are independent. They are not.
What follows is what the shipped stacks actually apply, read from published config or source on 23 August 2026: the numbers inside the GR00T N1.7 processor config, the augmentation code printed in the Pi0.5 appendix, lerobot's default transform set, and the ablations that say which of these earn their place. Then the part nobody writes down: which augmentations corrupt the action label on a single-arm setup like the SO-100.
What you need to know
- •Photometric augmentation (brightness, contrast, saturation, hue) is safe. It changes pixels, not geometry, so the recorded action stays correct.
- •Small random crops are the one geometric augmentation everyone ships. GR00T N1.7 and Pi0.5 keep 95 percent of the frame, Diffusion Policy and robomimic keep 90 percent.
- •robomimic measured the cost of removing pixel shift randomisation: 47 percent relative drop on Square, 35 percent on Transport.
- •Rotation is off or tiny. GR00T N1.7 ships random_rotation_angle = 0; Pi0.5 and lerobot's default affine cap at 5 degrees.
- •Horizontal flips appear in none of the shipped manipulation recipes read here. BC-Z reflects only its human video branch.
- •Enabling lerobot image transforms also enables a RandomAffine entry that fires on half your frames, drawn independently per camera.
- •Augmentation buys robustness to lighting and colour, not a new camera angle, object or grasp.
Why a cat can be flipped and a grasp cannot
One test settles every candidate: after the transform, is the recorded action still correct for the transformed image? Colour jitter passes. A 20 percent brighter scene still needs the gripper to close in the same place at the same moment. Horizontal flip fails. In the flipped image the cube sits on the left, but the recorded action still reaches right.
Nothing crashes. You get label noise, and label noise in imitation learning has a signature on the real arm: training loss falls to a healthy-looking number, then the policy approaches the object and hesitates. A model trained on contradictory image-action pairs averages them, and the average of reaching left and reaching right is stopping in the middle. That is the symptom on loss falls, policy does nothing.
An operation is an augmentation only if the recorded action stays correct after it. Otherwise it is label noise with a useful-sounding name. Photometric changes almost always pass. Geometric changes need the action transformed too, and on a single arm with an off-centre camera there usually is no correct action transform to apply.
What the shipped stacks actually apply
Rather than argue from first principles, here is what five widely used stacks send to the model. Treat the version labels as load-bearing: this part of every repo moves.
| Stack (version read) | Crop | Rotation | Flip | Colour jitter |
|---|---|---|---|---|
| GR00T N1.7 (processor_config.json) | crop_fraction 0.95, shortest edge 256 | random_rotation_angle = 0 | none | brightness 0.3, contrast 0.4, saturation 0.5, hue 0.08 |
| GR00T N1.5 (Isaac-GR00T, n1.5-release) | VideoCrop scale 0.95 | VideoRandomRotation exists, no data config uses it | VideoHorizontalFlip exists, no data config uses it | same four numbers, all seven data configs |
| Pi0.5 (arXiv 2504.16054, appendix) | RandomCrop at 95 percent, then resize back | Rotate(-5, 5) degrees | none | brightness 0.3, contrast 0.4, saturation 0.5 |
| lerobot defaults (v0.5.1 and main, off unless enabled) | none | RandomAffine degrees (-5.0, 5.0), translate 0.05 | none | ColorJitter on all four channels, plus SharpnessJitter |
| Diffusion Policy / robomimic | random crop to 90 percent, centre crop at eval | none | none | none |
Two things stand out. Brightness 0.3, contrast 0.4 and saturation 0.5 appear verbatim in both NVIDIA's and Physical Intelligence's stacks. That is an observation, not a causal claim, but when two labs that do not share code land on the same three numbers it is a place to start. The two models sit side by side on the GR00T N1.7 against Pi0.5 comparison.
# arXiv 2504.16054, pi-0.5 appendix, applied to all input images in this order
transforms = [
augmax.RandomCrop(int(width * 0.95), int(height * 0.95)),
augmax.Resize(width, height),
augmax.Rotate((-5, 5)),
augmax.ColorJitter(brightness=0.3, contrast=0.4, saturation=0.5),
]GR00T publishes the same information in a less quotable place: the processor config inside the base checkpoint. Fine-tune GR00T N1.7 without overriding anything and these are the values that run.
// nvidia/GR00T-N1.7-3B, processor_config.json -> processor_kwargs
"crop_fraction": 0.95,
"shortest_image_edge": 256,
"image_target_size": [256, 256], // legacy fallback, unused when crop_fraction is set
"image_crop_size": [230, 230], // legacy fallback, unused when crop_fraction is set
"random_rotation_angle": 0,
"color_jitter_params": {
"brightness": 0.3, "contrast": 0.4, "saturation": 0.5, "hue": 0.08
},
"use_albumentations": true,
"state_dropout_prob": 0.2
Colour jitter: the cheap win
Photometric augmentation is the one nobody argues about. It passes the invariance test trivially and targets the failure mode that bites on a desk robot: you record in the afternoon, run the checkpoint in the evening, the light has changed, and the policy behaves differently. A vision-language-action model with a pretrained backbone is fairly robust to that, but fine-tuning is where you teach it that your lighting is part of the task.
There is a conversion trap. GR00T and Pi0.5 quote jitter as torchvision-style magnitudes, where brightness 0.3 means a factor sampled uniformly from 0.7 to 1.3. lerobot's transform config wants explicit ranges. lerobot's own GR00T documentation translates between the two, and the translation is exact.
| Parameter | GR00T magnitude | Range it implies | lerobot tfs kwargs in the GR00T doc |
|---|---|---|---|
| brightness | 0.3 | 0.7 to 1.3 | [0.7, 1.3] |
| contrast | 0.4 | 0.6 to 1.4 | [0.6, 1.4] |
| saturation | 0.5 | 0.5 to 1.5 | [0.5, 1.5] |
| hue | 0.08 | -0.08 to 0.08 | [-0.08, 0.08] |
That is not a coincidence either. When lerobot added a GR00T training path, its documented recipe rebuilds NVIDIA's jitter by hand and throws away the rest of the default set while doing it.
# from the lerobot GR00T policy docs, the LIBERO training recipe
IMAGE_TRANSFORMS='{
"brightness": {"weight": 1.0, "type": "ColorJitter", "kwargs": {"brightness": [0.7, 1.3]}},
"contrast": {"weight": 1.0, "type": "ColorJitter", "kwargs": {"contrast": [0.6, 1.4]}},
"saturation": {"weight": 1.0, "type": "ColorJitter", "kwargs": {"saturation": [0.5, 1.5]}},
"hue": {"weight": 1.0, "type": "ColorJitter", "kwargs": {"hue": [-0.08, 0.08]}}
}'
lerobot-train \
--dataset.repo_id=IPEC-COMMUNITY/libero_spatial_no_noops_1.0.0_lerobot \
--dataset.image_transforms.enable=true \
--dataset.image_transforms.max_num_transforms=4 \
--dataset.image_transforms.tfs="$IMAGE_TRANSFORMS" \
--policy.type=groot \
--policy.base_model_path=nvidia/GR00T-N1.7-3Blerobot samples a subset of the configured transforms per call and the default subset size is 3. Supply four transforms and leave the default, and one is dropped at random every time. The GR00T recipe raises it to 4. Read from lerobot main on 23 August 2026.
Random crop: keep it small, and centre it at evaluation
Random crop is the exception in the geometric family and it has the strongest published evidence behind it. In the robomimic study, removing pixel shift randomisation caused a 47 percent relative drop on Square and 35 percent on Transport. The implementation is deliberately conservative: 76 from 84 pixels, 108 from 120, 216 from 240.
Diffusion Policy adopted the same approach and adds the detail that matters more than the crop size: at inference it takes a static centre crop of the same size. Across four independent stacks the crop lands in a narrow band.
- robomimic: 76 of 84, 108 of 120, 216 of 240 pixels, a consistent 90 percent.
- Diffusion Policy: 2x84x84 becomes 2x76x76 in simulation, 2x320x240 becomes 2x288x216 on the real robot. Also 90 percent.
- GR00T N1.7: shortest edge resized to 256, then crop_fraction 0.95, so a 256 by 256 view keeps a 243 by 243 window before being resized back.
- Pi0.5: 95 percent, then resized back to the original width and height.
Every stack that crops randomly during training switches to a deterministic centre crop for evaluation. GR00T builds two pipelines for exactly this reason, one with FractionalRandomCrop and one with FractionalCenterCrop. Crop randomly at inference and you inject viewpoint jitter at the moment the gripper needs precision. Skip the crop entirely and you change the field of view relative to training, which is worse.
Geometric augmentation and the action label
Now the part that costs people a weekend. Crop works because it is a small, zero-mean perturbation that keeps the object roughly where it was and is undone by a centre crop at test time. The rest of the geometric family lacks that property.
Flips
A horizontal flip looks like it doubles the dataset for free. It does not. The action in a LeRobot episode is a command that was actually sent to the arm, so mirroring the image without mirroring the action produces a frame whose correct answer is the mirror of its label. Mirroring the action too requires a symmetry a single arm does not have.
BC-Z is the interesting data point, because it uses reflections and is careful about where. Its policy input gets random crops, downsampling and standard photometric augmentation. Random reflections along the x and y axes go only to the human demonstration videos feeding the task embedding, sampled once per video, applied identically to all 20 frames, and none at inference. Longer version in the BC-Z write-up.
Rotation
Rotation about the optical axis means physically rolling the camera. For a fixed third-person camera that is a pose the robot will never see. For a wrist camera it is worse: the image really does rotate when the wrist rotates, and that rotation correlates with the joint state the policy is predicting, so fake rotation puts noise on top of a genuine cue. GR00T N1.7 ships random_rotation_angle at 0 while keeping the code path.
Translation and the wrist camera
Translation is random crop's less careful cousin. A few percent is the same regulariser: it stops the encoder keying on absolute pixel coordinates. Beyond that it breaks the mapping from image position to workspace position, which is what the policy uses in the final centimetres of a grasp. robomimic measured the wrist view too: removing the wrist camera caused a 43 percent relative drop on Transport.
- Small crops and shifts stop the encoder memorising absolute pixel positions, the best-evidenced augmentation result in manipulation.
- No extra recording time and no extra GPU memory.
- The cheapest defence against a camera nudged between recording and deployment.
- Every major stack ships some version of it.
- Past roughly 10 percent of the frame it disagrees with the action label instead of regularising.
- Flips and large rotations create pairs that contradict the dataset, and the model averages the contradiction into hesitation.
- It cannot invent the parallax of a new camera angle.
- On wrist cameras it perturbs the signal that controls grasp alignment.
Passing --dataset.image_transforms.enable=true to lerobot-train does not just turn on colour jitter. The default set has six entries and one is RandomAffine with degrees=(-5.0, 5.0) and translate=(0.05, 0.05). Because max_num_transforms defaults to 3 and all six weights are 1.0, three of six are drawn without replacement per call, so the affine fires on half your frames. The draw happens once per camera key, so front and wrist get different rotations of the same instant. Verified against v0.5.1 and main.
One draw per sample, or one per camera?
This detail is invisible in the config and shows up in behaviour. GR00T applies augmentation through an albumentations ReplayCompose: the first image creates the random parameters and every later image replays them. The handle is created once and carried across the loop over camera views, so front and wrist views of the same moment get the same crop offset and the same colour shift.
# huggingface/lerobot, src/lerobot/datasets/dataset_reader.py, main, 23 Aug 2026
if self._image_transforms is not None:
for cam in self._meta.camera_keys:
if cam in self._meta.depth_keys:
continue
item[cam] = self._image_transforms(item[cam])
# NVIDIA/Isaac-GR00T, gr00t/model/gr00t_n1d7/processing_gr00t_n1d7.py, main, 23 Aug 2026
replay = None
for view in image_keys:
transformed_images, replay = apply_with_replay(
image_transform, view_images, view_masks, replay
)Which you want depends on what you are making the model robust to. Independent draws simulate cameras with different white balance and mounting. Shared draws keep multi-view geometry self-consistent, which matters when the policy fuses views to localise one object. Neither is wrong. What is wrong is not knowing which your stack does, then wondering why colour augmentation on a two-camera LeRobot dataset behaves differently from the single-camera run you tested.
Look at the frames before you spend a GPU hour
Both stacks ship a preview tool and both take under a minute. Augmentation bugs are obvious in a contact sheet and invisible in a loss curve.
- 1Render lerobot's transforms on your own dataset
Writes one PNG per example under
outputs/image_transforms, once for the combined pipeline and once for each transform alone. Note the extra: the viewer scripts sit behinddataset_viz, nottraining.bashpip install "lerobot[dataset_viz]" lerobot-imgtransform-viz \ --repo_id=lerobot/pusht \ --episodes='[0]' \ --image_transforms.enable=True - 2Preview GR00T's mask-guided augmentation
Writes side by side comparisons of original, augmented and mask under
output_dir/<view_name>/. It needs a dataset with masks, and the repo ships one.bashcd examples/mask-guided-background-suppression uv run python test_extra_augmentation.py \ --dataset_path ../../demo_data/cube_to_bowl_5_with_mask \ --embodiment_tag NEW_EMBODIMENT \ --modality_config_path so101_config.py \ --extra_augmentation_config '{"background_noise_transforms": [{"target_mask_values": [0], "p": 1.0}]}' \ --output_dir /tmp/augmentation_vis --num_frames 5 - 3Change the jitter on a real GR00T fine-tune
examples/finetune.shforwards tolaunch_finetune.py, a tyro CLI over FinetuneConfig. The colour jitter default is written out in the shell script, so you see what you override. crop_fraction and shortest_image_edge are documented as a pair.bash# examples/finetune.sh default, verbatim: # COLOR_JITTER_PARAMS="brightness 0.3 contrast 0.4 saturation 0.5 hue 0.08" CUDA_VISIBLE_DEVICES=0 NUM_GPUS=1 uv run bash examples/finetune.sh \ --base-model-path nvidia/GR00T-N1.7-3B \ --dataset-path examples/SO100/finish_sandwich_lerobot/izuluaga/finish_sandwich \ --modality-config-path examples/SO100/so100_config.py \ --embodiment-tag NEW_EMBODIMENT \ --shortest-image-edge 256 \ --crop-fraction 0.9 \ --color-jitter-params "brightness 0.4 contrast 0.4 saturation 0.5 hue 0.08" \ --output-dir /tmp/so100_finetune
The Isaac-GR00T fine-tuning entry point exposes no seed, so two runs with identical settings are not bit-for-bit identical and a small difference between an augmented and an unaugmented run proves nothing. lerobot has a seed and defaults it to 1000, which makes ACT and SmolVLA the better platform for a controlled augmentation experiment.
Two ways to run the comparison
Whether augmentation helps on your task has a cheap answer: train the same dataset twice and drive the arm. What differs is how much plumbing sits between you and that answer.
You clone the trainer, mount the dataset, rent or own the GPU, and run the pair yourself. This is the only path if you want augmentation parameters the trainer form does not expose, which today is all of them.
- 1Convert the dataset if you are training GR00T
The GR00T loader wants LeRobot v2.x. The converter carries its own pyproject, so run it from its own directory.
bashcd scripts/lerobot_conversion uv venv source .venv/bin/activate uv pip install -e . python convert_v3_to_v2.py --repo-id <your-user>/<your-dataset> - 2Run the baseline with augmentation off
For ACT or SmolVLA this is the default, since image transforms are off unless enabled. Fix the seed so the comparison means something.
bashlerobot-train \ --dataset.repo_id=<your-user>/<your-dataset> \ --policy.type=act \ --seed=1000 \ --output_dir=outputs/train/act_noaug - 3Run the same thing with colour only
Supply your own tfs dict rather than only flipping enable to true, so the default RandomAffine does not join uninvited.
bashlerobot-train \ --dataset.repo_id=<your-user>/<your-dataset> \ --dataset.image_transforms.enable=true \ --dataset.image_transforms.max_num_transforms=4 \ --dataset.image_transforms.tfs="$IMAGE_TRANSFORMS" \ --policy.type=act \ --seed=1000 \ --output_dir=outputs/train/act_colour - 4Evaluate on the arm, not on the loss
Augmentation raises training loss and is supposed to. Judge it by success rate under changed light or a moved lamp.
Two runs, not one. If the difference matters it is worth the second GPU hour; if it does not, spend the time recording more episodes.
The training form picks model, dataset and hyperparameters, rents a GPU by required VRAM, runs the trainer and writes checkpoints to object storage. The extra knobs are saveSteps for the GR00T models, seed and logFreq for Pi0.5 and SmolVLA, and chunkSize, nActionSteps, seed and logFreq for ACT. There is no augmentation switch: what runs is each trainer's default, the table at the top of this article.
So the platform helps with the half augmentation cannot solve: getting real variation into the dataset. The desktop client records LeRobot-format episodes straight from a teleoperation session.
- 1Record the variation instead of simulating it
Record across the conditions you care about: morning light, evening light, lamp on, lamp off, mat rotated. See record your first dataset.
- 2Check what you recorded
Open the dataset in the dataset directory before training. Two cameras swapped in some episodes is a problem no augmentation setting will fix.
- 3Train the same dataset on two models
Since you cannot vary the augmentation, vary what you can. GR00T N1.7 on SO-100 arrives with crop and jitter on; ACT on SO-100 arrives with none. Confounded with everything else about the models, but a real comparison of two regimes.
- 4Run it back on the arm
Inference auto-provisions a pod serving the policy. Pods carry an idle watchdog and destroy themselves, so a forgotten experiment does not keep billing.
| Tier | Models | Typical run | Cost per run |
|---|---|---|---|
| A100 80 GB or H100 | GR00T N1.7, GR00T N1.5, Pi0.5 | 3 to 6 hours at 1.20 to 2.00 USD per hour | about 4 to 12 USD |
| RTX 4090 or any 24 GB card | SmolVLA, ACT | 2 to 5 hours at 0.30 to 0.60 USD per hour | about 1 to 3 USD |

What augmentation will not fix
Augmentation operates on pixels that already exist. It cannot create parallax, it cannot create a grasp you never demonstrated, and it cannot resolve a contradiction inside your dataset. The training documentation lists what each trainer runs.
- A new camera angle. Cropping a fixed view does not produce the occlusion pattern of a different viewpoint. Move the camera and record again.
- A new object or background. GreenAug is the sharpest data point: record in front of a green screen, then chroma-key real backgrounds in. Its best variant improved on no augmentation by about 65 percent, on standard computer vision augmentation by about 29 percent and on generative augmentation by about 21 percent, all relative, across over 850 demonstrations and 8.2k evaluation episodes.
- Inconsistent camera assignment. Swapped front and wrist streams are a device identification problem, the same family as a camera that is not detected.
- Too few episodes. GR00T and Pi0.5 want at least 50 episodes here, SmolVLA at least 30.
- A policy that only works in one setup. Start at that failure mode page.
Latency deserves plain speech, because it is the limit of remote inference generally. The per-step inference latency of these models ranges from 20 ms for ACT to 485 ms for Pi0.5, and public internet round trips on top turn a working policy into a hesitant one. Remote inference is viable for slow pick and place, not fast reactive motion.

RT-1 is a useful sanity check. Its paper describes the input pipeline in detail, six images at 300 by 300 into an ImageNet-pretrained EfficientNet-B3, and never describes an augmentation stack at all. What it does ablate is the pretrained initialisation: removing ImageNet pre-training decreased unseen task performance by 33 percent. Backbone and data first, pixel transforms second. See how to collect high quality VLA training data and the broader VLA overview.
Defaults worth starting from
For a single arm with one fixed camera and one wrist camera, this is the position supported by everything above.
| Augmentation | Setting | Why |
|---|---|---|
| Brightness / contrast / saturation | 0.3 / 0.4 / 0.5 magnitudes | Two independent stacks converged on these. |
| Hue | 0.08 | GR00T ships it, Pi0.5 leaves it out. |
| Random crop | 0.90 to 0.95 of the frame | Best-evidenced augmentation in manipulation. |
| Centre crop at evaluation | Same size as the training crop | Train and eval must see the same field of view. |
| Rotation | 0, or at most 5 degrees | GR00T ships 0. Nothing read here goes beyond 5. |
| Translation / affine | Off, unless it is the crop | Redundant with the crop and harder to bound. |
| Horizontal flip | Off | It contradicts the action label on an asymmetric workspace. |
| Background replacement | Only with a green screen or masks | Largest measured win in GreenAug, and the most setup work. |
Before touching any of this, train once with the defaults your model already ships and drive the arm under two lighting conditions. If it holds up, augmentation is not your bottleneck and the next twenty episodes are worth more than the next twenty parameter tweaks. Start at train your first policy or compare options on the policies page.
Train the same dataset with the defaults that ship
Pick a model and an arm, and the guide shows the exact hyperparameters the trainer sends, including the augmentation each stack applies by default. GR00T N1.7, GR00T N1.5, Pi0.5, SmolVLA and ACT, on SO-100, SO-101, Koch v1.1 and LeKiwi.
Open the training guidesDoes colour jitter ever hurt a manipulation policy?▾
In one case: when colour is the task signal. If the policy must pick the red block from the blue block, a saturation range of 0.5 to 1.5 plus a hue shift of plus or minus 0.08 can push a washed-out red towards something the model reads as orange. For colour-discriminative tasks, keep brightness and contrast and shrink hue and saturation. Otherwise the 0.3 / 0.4 / 0.5 / 0.08 that GR00T ships is a reasonable start.
Can I mirror my dataset to double the number of episodes?▾
Not without mirroring the actions, and on a single arm there is usually no correct mirror: the joint that swings left is not the joint that swings right, and the camera is not on the symmetry axis. BC-Z, which does use reflections, applies them only to the human video branch that produces the task embedding, never to the policy's image input.
How much does random crop help in practice?▾
The best measurement available is robomimic's: removing pixel shift randomisation caused a 47 percent relative drop on Square and 35 percent on Transport. That is a simulation benchmark with a specific architecture, so treat it as an order of magnitude. The consistent part across stacks is the size, 90 to 95 percent of the frame, with a deterministic centre crop at evaluation.
Does the AY-Robots training form let me change augmentation?▾
No. The extra knobs are saveSteps for the GR00T models, seed and logFreq for Pi0.5 and SmolVLA, and chunkSize, nActionSteps, seed and logFreq for ACT. Augmentation runs at each trainer's default. To sweep jitter ranges or crop fractions, run the trainer yourself with the commands above.
Why does GR00T apply the same crop to both cameras?▾
It uses an albumentations ReplayCompose and carries one replay handle across the loop over camera views, so parameters drawn for the first image are reused for every other view and timestep in that sample. Its mask-guided example states the intent: standard augmentations stay consistent across camera views within the same timestep, while mask-based transforms run per frame. lerobot calls its transform once per camera key, so each camera draws fresh parameters.
Sources
- nvidia/GR00T-N1.7-3B processor_config.json: crop_fraction 0.95, shortest_image_edge 256, random_rotation_angle 0, color_jitter_params
- Isaac-GR00T image_augmentations.py: FractionalRandomCrop, FractionalCenterCrop, apply_with_replay and the train/eval pipeline split
- Isaac-GR00T processing_gr00t_n1d7.py: one replay handle carried across the loop over camera views
- Isaac-GR00T examples/finetune.sh: default COLOR_JITTER_PARAMS brightness 0.3 contrast 0.4 saturation 0.5 hue 0.08, no seed flag
- Isaac-GR00T mask-guided background suppression: extra_augmentation_config, per-frame mask transforms, replayed standard transforms
- Isaac-GR00T n1.5-release data configs: VideoCrop scale 0.95 and VideoColorJitter in all seven configs
- lerobot ImageTransformsConfig on main: enable False, max_num_transforms 3, six default transforms including RandomAffine
- lerobot dataset_reader.py: image transforms applied once per camera key
- lerobot GR00T policy documentation: colour-only IMAGE_TRANSFORMS recipe and preliminary LIBERO results
- What Matters in Learning from Offline Human Demonstrations for Robot Manipulation (robomimic), Mandlekar et al., 2021
- Diffusion Policy: Visuomotor Policy Learning via Action Diffusion, Chi et al., v5 2024
- pi-0.5: a Vision-Language-Action Model with Open-World Generalization, Physical Intelligence, 2025
- BC-Z: Zero-Shot Task Generalization with Robotic Imitation Learning, Jang et al., 2022
- RT-1: Robotics Transformer for Real-World Control at Scale, Brohan et al., 2022
- Green Screen Augmentation Enables Scene Generalisation in Robotic Manipulation, Teoh et al., 2024
Ready for high-quality robotics data?
AY-Robots connects your robots to skilled operators worldwide.
Get Started