rtabmap_python tests and doc (#1455)

This commit is contained in:
matlabbe
2026-09-13 12:20:17 -07:00
committed by GitHub
parent 5062bf0614
commit 73c98f87a8
8 changed files with 355 additions and 9 deletions
+178
View File
@@ -0,0 +1,178 @@
# Copyright 2025 matlabbe
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# * Neither the name of the matlabbe nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
"""Tests for :mod:`rtabmap_python.cv_compression`."""
import struct
import zlib
import numpy as np
import pytest
from rtabmap_python.cv_compression import compress, uncompress
# The single-channel depths the format encodes, with the codes RTAB-Map's
# serializeMatType() gives them: CV_8U, CV_8S, CV_16U, CV_16S, CV_32S, CV_32F, CV_64F.
SUPPORTED_TYPES = [
('uint8', 0),
('int8', 1),
('uint16', 2),
('int16', 3),
('int32', 4),
('float32', 5),
('float64', 6),
]
# Three int32: rows, cols, type code.
TRAILER_SIZE = 3 * 4
@pytest.mark.parametrize('dtype,code', SUPPORTED_TYPES)
def test_roundtrip_preserves_shape_dtype_and_values(dtype, code):
"""Every supported depth survives a compress/uncompress cycle unchanged."""
data = np.arange(12, dtype=dtype).reshape(3, 4)
result = uncompress(compress(data))
assert result.shape == (3, 4)
assert result.dtype == np.dtype(dtype)
assert np.array_equal(result, data)
@pytest.mark.parametrize('dtype,code', SUPPORTED_TYPES)
def test_trailer_records_rows_cols_and_type_code(dtype, code):
"""The last 12 bytes are rows, cols and the type code, as the C++ side writes them."""
data = np.zeros((3, 4), dtype=dtype)
trailer = bytes(compress(data)[-TRAILER_SIZE:])
assert trailer == struct.pack('iii', 3, 4, code)
def test_payload_is_plain_zlib_of_the_c_order_bytes():
"""Everything before the trailer is a zlib stream, so the C++ side can inflate it."""
data = np.arange(6, dtype=np.uint8)
payload = bytes(compress(data)[:-TRAILER_SIZE])
assert zlib.decompress(payload) == data.tobytes()
def test_compress_returns_a_bytearray():
"""The return type is a bytearray, which is what the message fields expect."""
assert isinstance(compress(np.zeros(4, dtype=np.uint8)), bytearray)
def test_one_dimensional_input_comes_back_as_a_single_row():
"""A 1-D array is recorded as 1-by-n, so the roundtrip is not shape-preserving."""
data = np.arange(5, dtype=np.float32)
result = uncompress(compress(data))
assert result.shape == (1, 5)
assert np.array_equal(result.ravel(), data)
@pytest.mark.parametrize('shape', [(1, 5), (5, 1), (2, 3)])
def test_two_dimensional_shapes_are_preserved_exactly(shape):
"""Rows and cols are recorded separately, so no 2-D shape is transposed or flattened."""
data = np.arange(5 if 1 in shape else 6, dtype=np.uint8).reshape(shape)
assert uncompress(compress(data)).shape == shape
def test_non_contiguous_input_roundtrips():
"""A transposed view is written in C order, so it reads back as the same matrix."""
data = np.arange(12, dtype=np.int16).reshape(3, 4).T
assert not data.flags.c_contiguous
result = uncompress(compress(data))
assert result.shape == (4, 3)
assert np.array_equal(result, data)
def test_empty_array_roundtrips_as_an_empty_row():
"""An empty array is not a special case; it comes back as a 1-by-0 matrix."""
result = uncompress(compress(np.array([], dtype=np.uint8)))
assert result.shape == (1, 0)
assert result.dtype == np.uint8
def test_uncompressed_array_is_read_only():
"""uncompress() views the decompressed buffer rather than copying it."""
result = uncompress(compress(np.arange(4, dtype=np.uint8)))
assert not result.flags.writeable
with pytest.raises(ValueError):
result[0, 0] = 1
# .copy() is the way out, as the docstring says.
assert result.copy().flags.writeable
def test_accepts_bytes_as_well_as_bytearray():
"""uncompress() reads whatever compress() produced, converted or not."""
data = np.arange(8, dtype=np.uint16).reshape(2, 4)
result = uncompress(bytes(compress(data)))
assert np.array_equal(result, data)
def test_larger_matrix_roundtrips():
"""A matrix big enough to actually exercise zlib, with non-trivial content."""
rng = np.random.default_rng(42)
data = rng.integers(0, 255, size=(120, 160), dtype=np.uint8)
assert np.array_equal(uncompress(compress(data)), data)
def test_rejects_more_than_two_dimensions():
"""The format has no encoding for a third dimension, so compress() refuses one."""
with pytest.raises(AssertionError):
compress(np.zeros((2, 2, 2), dtype=np.uint8))
@pytest.mark.parametrize('dtype', ['int64', 'uint64', 'float16'])
def test_rejects_unsupported_dtype(dtype):
"""Depths outside CV_8U..CV_64F have no type code and raise rather than truncate."""
with pytest.raises(KeyError):
compress(np.zeros(4, dtype=dtype))
def test_rejects_unknown_type_code():
"""A trailer from a multi-channel C++ matrix encodes a code this table lacks."""
payload = bytes(compress(np.zeros((2, 2), dtype=np.uint8))[:-TRAILER_SIZE])
# What serializeMatType() returns for a 3-channel CV_8U matrix: depth + ((cn - 1) << 3).
forged = bytearray(payload) + struct.pack('iii', 2, 2, 0 + ((3 - 1) << 3))
with pytest.raises(KeyError):
uncompress(forged)