|
5 | 5 | import io |
6 | 6 | from pathlib import Path |
7 | 7 |
|
| 8 | +import cv2 |
8 | 9 | import h5py |
9 | 10 | import numpy as np |
10 | 11 | import pytest |
@@ -59,10 +60,18 @@ def write_calibration(root: Path) -> Path: |
59 | 60 |
|
60 | 61 |
|
61 | 62 | def encode_jpeg(color: tuple[int, int, int], height: int = 18, width: int = 20) -> bytes: |
62 | | - image = Image.new("RGB", (width, height), color) |
63 | | - buffer = io.BytesIO() |
64 | | - image.save(buffer, format="JPEG", quality=100, subsampling=0) |
65 | | - return buffer.getvalue() |
| 63 | + """Encode ``color`` (RGB) the way the RoboDojo recorder does. |
| 64 | +
|
| 65 | + It hands an RGB array straight to ``cv2.imencode``, which expects BGR, so R |
| 66 | + and B end up swapped inside the file. Fixtures have to reproduce that or the |
| 67 | + reader's ``cv2.imdecode`` — which exists to undo it — looks wrong under test |
| 68 | + while being right on real data. |
| 69 | + """ |
| 70 | + rgb = np.zeros((height, width, 3), dtype=np.uint8) |
| 71 | + rgb[:, :] = color |
| 72 | + ok, buffer = cv2.imencode(".jpg", rgb, [int(cv2.IMWRITE_JPEG_QUALITY), 100]) |
| 73 | + assert ok, "cv2.imencode failed" |
| 74 | + return buffer.tobytes() |
66 | 75 |
|
67 | 76 |
|
68 | 77 | def source_arrays(T: int) -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]: |
@@ -856,3 +865,25 @@ def test_registry_constructs_robodojo_and_exports_classes(tmp_path: Path): |
856 | 865 | assert isinstance(dataset, MultiTaskRoboDojoDataset) |
857 | 866 | assert isinstance(dataset._sub_datasets[0], RoboDojoDataset) |
858 | 867 | assert dataset[0]["action"].shape == (2, 80) |
| 868 | + |
| 869 | + |
| 870 | +def test_decode_jpeg_undoes_the_recorder_channel_swap(): |
| 871 | + """The reader must return true RGB, not the stored order. |
| 872 | +
|
| 873 | + RoboDojo writes its JPEGs with ``cv2.imencode`` fed an RGB array, so the file |
| 874 | + holds R and B swapped. Decoding with PIL returns that stored order and feeds |
| 875 | + the model channel-swapped frames, which is what this reader did until it was |
| 876 | + caught against the corpora's own preview videos. Pinned here because CI never |
| 877 | + sees a real episode. |
| 878 | + """ |
| 879 | + from openwam.dataloader.robodojo import _decode_jpeg |
| 880 | + |
| 881 | + red = (230, 20, 15) |
| 882 | + frame = np.asarray(_decode_jpeg(encode_jpeg(red), source="test")) |
| 883 | + |
| 884 | + assert frame[0, 0, 0] > 200, "red must come back in channel 0" |
| 885 | + assert frame[0, 0, 2] < 60, "blue must not have been swapped into channel 0" |
| 886 | + |
| 887 | + # And the PIL reading of the very same bytes is the swap this guards against. |
| 888 | + stored = np.asarray(Image.open(io.BytesIO(encode_jpeg(red))).convert("RGB")) |
| 889 | + assert stored[0, 0, 2] > 200 and stored[0, 0, 0] < 60 |
0 commit comments