The two HDF5 dataloaders decode their JPEG camera buffers with opposite channel conventions. robotwin.py uses cv2.imdecode and documents exactly why; robodojo.py uses PIL and says nothing about it. I could not settle which is right for RoboDojo from the repo alone, so this is a question rather than a bug report — but the asymmetry looked worth raising.
The two decoders
openwam/dataloader/robotwin.py:624-634 (line numbers on main):
def _decode_jpeg(self, jpeg_bytes) -> Image.Image:
"""Decode JPEG bytes from HDF5 to a PIL RGB image.
RoboTwin encodes frames by passing RGB arrays directly to
``cv2.imencode`` (which expects BGR), so R and B channels are
swapped inside the JPEG. Using ``cv2.imdecode`` reverses this
swap, giving back the original RGB order — no further
conversion needed.
"""
arr = cv2.imdecode(np.frombuffer(bytes(jpeg_bytes), np.uint8), cv2.IMREAD_COLOR)
openwam/dataloader/robodojo.py:359-365:
def _decode_jpeg(value: Any, *, source: str) -> Image.Image:
...
with Image.open(io.BytesIO(encoded)) as image:
return image.convert("RGB").copy()
PIL.Image.open(...).convert("RGB") returns channels in stored order; cv2.imdecode returns BGR, which is what undoes a cv2.imencode of an RGB array. The two paths therefore disagree by an R/B swap whenever both datasets were written by the same kind of encoder.
Why it may matter
git grep -nE 'COLOR_BGR2RGB|COLOR_RGB2BGR|\[\.\.\., ::-1\]|\[:, :, ::-1\]' -- openwam/dataloader/ returns nothing on main, so neither loader corrects afterwards, and the deploy path does no conversion either — openwam/deploy/obs_preprocess.py:142,148 decodes incoming frames with Image.open(...).convert("RGB") and hands them on. So a model trained through robodojo sees whatever PIL yields, and at eval sees whatever the client encoded, with nothing in between to reconcile them.
If RoboDojo's vision/<camera>/colors buffers were written the way robotwin.py describes for RoboTwin — an RGB array passed to cv2.imencode — then robodojo.py trains on channel-swapped frames and evaluates on correct ones.
What I could not determine
Whether that "if" holds. OpenWAM ships no RoboDojo encoder — grep -rn 'imencode|imwrite|encode_jpeg' --include='*.py' openwam/ scripts/ benchmarks/ finds only robotwin.py's comment, benchmarks/ebench/mock_genmanip_server.py (which encodes with turbojpeg TJPF_RGB, i.e. RGB-correct), and one cv.imwrite in the RoboTwin benchmark client that converts to BGR first. So the RoboDojo recorder is external to this repo and I have no RoboDojo trajectories here to test against.
If the RoboDojo buffers are RGB-correct, robodojo.py is right and the only thing missing is a comment saying so — which would still be worth having next to a sibling that documents the opposite convention.
Anyone with a trajectory file can settle it in a few lines:
import io, numpy as np, cv2
from PIL import Image
buf = ... # one entry from vision/<camera>/colors
pil = np.asarray(Image.open(io.BytesIO(buf)).convert("RGB"))
cv = cv2.imdecode(np.frombuffer(buf, np.uint8), cv2.IMREAD_COLOR)
print(pil[0, 0], cv[0, 0]) # equal only if the frame is grey at that pixel
then check which ordering matches the scene — a wooden table or a red block makes it obvious by eye.
Related
The same decode is reported downstream against the XPolicyLab adapter in XPolicyLab/XPolicyLab#116, where the eval side is known to be RGB. The code there is a vendored copy of this loader, and robodojo.py's own module docstring notes that the Isaac eval runtime keeps pinned copies of these contracts — so if the swap is real, it is real in both places and worth fixing at the source.
Verified on Python 3.10.20, torch 2.7.1+cu128, at main (f6bae3e). Happy to send a PR — either the cv2.imdecode treatment robotwin.py already uses, or just the clarifying comment — once someone with the data says which way it goes.
The two HDF5 dataloaders decode their JPEG camera buffers with opposite channel conventions.
robotwin.pyusescv2.imdecodeand documents exactly why;robodojo.pyuses PIL and says nothing about it. I could not settle which is right for RoboDojo from the repo alone, so this is a question rather than a bug report — but the asymmetry looked worth raising.The two decoders
openwam/dataloader/robotwin.py:624-634(line numbers onmain):openwam/dataloader/robodojo.py:359-365:PIL.Image.open(...).convert("RGB")returns channels in stored order;cv2.imdecodereturns BGR, which is what undoes acv2.imencodeof an RGB array. The two paths therefore disagree by an R/B swap whenever both datasets were written by the same kind of encoder.Why it may matter
git grep -nE 'COLOR_BGR2RGB|COLOR_RGB2BGR|\[\.\.\., ::-1\]|\[:, :, ::-1\]' -- openwam/dataloader/returns nothing onmain, so neither loader corrects afterwards, and the deploy path does no conversion either —openwam/deploy/obs_preprocess.py:142,148decodes incoming frames withImage.open(...).convert("RGB")and hands them on. So a model trained throughrobodojosees whatever PIL yields, and at eval sees whatever the client encoded, with nothing in between to reconcile them.If RoboDojo's
vision/<camera>/colorsbuffers were written the wayrobotwin.pydescribes for RoboTwin — an RGB array passed tocv2.imencode— thenrobodojo.pytrains on channel-swapped frames and evaluates on correct ones.What I could not determine
Whether that "if" holds. OpenWAM ships no RoboDojo encoder —
grep -rn 'imencode|imwrite|encode_jpeg' --include='*.py' openwam/ scripts/ benchmarks/finds onlyrobotwin.py's comment,benchmarks/ebench/mock_genmanip_server.py(which encodes with turbojpegTJPF_RGB, i.e. RGB-correct), and onecv.imwritein the RoboTwin benchmark client that converts to BGR first. So the RoboDojo recorder is external to this repo and I have no RoboDojo trajectories here to test against.If the RoboDojo buffers are RGB-correct,
robodojo.pyis right and the only thing missing is a comment saying so — which would still be worth having next to a sibling that documents the opposite convention.Anyone with a trajectory file can settle it in a few lines:
then check which ordering matches the scene — a wooden table or a red block makes it obvious by eye.
Related
The same decode is reported downstream against the XPolicyLab adapter in XPolicyLab/XPolicyLab#116, where the eval side is known to be RGB. The code there is a vendored copy of this loader, and
robodojo.py's own module docstring notes that the Isaac eval runtime keeps pinned copies of these contracts — so if the swap is real, it is real in both places and worth fixing at the source.Verified on Python 3.10.20, torch 2.7.1+cu128, at
main(f6bae3e). Happy to send a PR — either thecv2.imdecodetreatmentrobotwin.pyalready uses, or just the clarifying comment — once someone with the data says which way it goes.