Skip to content

binary format I/O fails on big-endian architectures like s390x due to endianness bugs #2511

@strophy

Description

@strophy

The following four tests fail on s390x (big-endian) under Alpine Linux 3.23 with errors related to endianness:

=================================== FAILURES ===================================
____________________________ PlyTest.test_uv_export ____________________________
[gw6] linux -- Python 3.12.12 /builds/strophy/aports/testing/py3-trimesh/src/trimesh-4.11.2/.testenv/bin/python3
self = <tests.test_ply.PlyTest testMethod=test_uv_export>
    def test_uv_export(self):
        m = g.get_mesh("fuze.ply")
        assert hasattr(m, "visual") and hasattr(m.visual, "uv")
        assert m.visual.uv.shape[0] == m.vertices.shape[0]
    
        # create empty file to export to
    
        with g.TemporaryDirectory() as D:
            name = g.os.path.join(D, "file.ply")
    
            # export should contain the uv data
            m.export(name)
            m2 = g.trimesh.load(name)
    
        assert hasattr(m2, "visual") and hasattr(m2.visual, "uv")
>       assert g.np.allclose(m.visual.uv, m2.visual.uv)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_ply.py:234: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.12/site-packages/numpy/_core/numeric.py:2376: in allclose
    res = all(isclose(a, b, rtol=rtol, atol=atol, equal_nan=equal_nan))
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
a = TrackedArray([[0.62901198864 , 0.417026013136],
              [0.62901198864 , 0.442966014147],
              [0.66143...8642],
              [0.565362989902, 0.991708993912],
              [0.520901024342, 0.780556023121]], shape=(664, 2))
b = TrackedArray([[4.092561360438e-312, 4.765015389936e-312],
              [4.092561360438e-312, 4.765015389936e-312],
  ... [4.092561360438e-312, 4.765015389936e-312],
              [4.092561360438e-312, 4.765015389936e-312]], shape=(502, 2))
rtol = 1e-05, atol = 1e-08, equal_nan = False
    @array_function_dispatch(_isclose_dispatcher)
    def isclose(a, b, rtol=1.e-5, atol=1.e-8, equal_nan=False):
        """
        Returns a boolean array where two arrays are element-wise equal within a
        tolerance.
    
        The tolerance values are positive, typically very small numbers.  The
        relative difference (`rtol` * abs(`b`)) and the absolute difference
        `atol` are added together to compare against the absolute difference
        between `a` and `b`.
    
        .. warning:: The default `atol` is not appropriate for comparing numbers
                     with magnitudes much smaller than one (see Notes).
    
        Parameters
        ----------
        a, b : array_like
            Input arrays to compare.
        rtol : array_like
            The relative tolerance parameter (see Notes).
        atol : array_like
            The absolute tolerance parameter (see Notes).
        equal_nan : bool
            Whether to compare NaN's as equal.  If True, NaN's in `a` will be
            considered equal to NaN's in `b` in the output array.
    
        Returns
        -------
        y : array_like
            Returns a boolean array of where `a` and `b` are equal within the
            given tolerance. If both `a` and `b` are scalars, returns a single
            boolean value.
    
        See Also
        --------
        allclose
        math.isclose
    
        Notes
        -----
        For finite values, isclose uses the following equation to test whether
        two floating point values are equivalent.::
    
         absolute(a - b) <= (atol + rtol * absolute(b))
    
        Unlike the built-in `math.isclose`, the above equation is not symmetric
        in `a` and `b` -- it assumes `b` is the reference value -- so that
        `isclose(a, b)` might be different from `isclose(b, a)`.
    
        The default value of `atol` is not appropriate when the reference value
        `b` has magnitude smaller than one. For example, it is unlikely that
        ``a = 1e-9`` and ``b = 2e-9`` should be considered "close", yet
        ``isclose(1e-9, 2e-9)`` is ``True`` with default settings. Be sure
        to select `atol` for the use case at hand, especially for defining the
        threshold below which a non-zero value in `a` will be considered "close"
        to a very small or zero value in `b`.
    
        `isclose` is not defined for non-numeric data types.
        :class:`bool` is considered a numeric data-type for this purpose.
    
        Examples
        --------
        >>> import numpy as np
        >>> np.isclose([1e10,1e-7], [1.00001e10,1e-8])
        array([ True, False])
    
        >>> np.isclose([1e10,1e-8], [1.00001e10,1e-9])
        array([ True, True])
    
        >>> np.isclose([1e10,1e-8], [1.0001e10,1e-9])
        array([False,  True])
    
        >>> np.isclose([1.0, np.nan], [1.0, np.nan])
        array([ True, False])
    
        >>> np.isclose([1.0, np.nan], [1.0, np.nan], equal_nan=True)
        array([ True, True])
    
        >>> np.isclose([1e-8, 1e-7], [0.0, 0.0])
        array([ True, False])
    
        >>> np.isclose([1e-100, 1e-7], [0.0, 0.0], atol=0.0)
        array([False, False])
    
        >>> np.isclose([1e-10, 1e-10], [1e-20, 0.0])
        array([ True,  True])
    
        >>> np.isclose([1e-10, 1e-10], [1e-20, 0.999999e-10], atol=0.0)
        array([False,  True])
    
        """
        # Turn all but python scalars into arrays.
        x, y, atol, rtol = (
            a if isinstance(a, (int, float, complex)) else asanyarray(a)
            for a in (a, b, atol, rtol))
    
        # Make sure y is an inexact type to avoid bad behavior on abs(MIN_INT).
        # This will cause casting of x later. Also, make sure to allow subclasses
        # (e.g., for numpy.ma).
        # NOTE: We explicitly allow timedelta, which used to work. This could
        #       possibly be deprecated. See also gh-18286.
        #       timedelta works if `atol` is an integer or also a timedelta.
        #       Although, the default tolerances are unlikely to be useful
        if (dtype := getattr(y, "dtype", None)) is not None and dtype.kind != "m":
            dt = multiarray.result_type(y, 1.)
            y = asanyarray(y, dtype=dt)
        elif isinstance(y, int):
            y = float(y)
    
        # atol and rtol can be arrays
        if not (np.all(np.isfinite(atol)) and np.all(np.isfinite(rtol))):
            err_s = np.geterr()["invalid"]
            err_msg = f"One of rtol or atol is not valid, atol: {atol}, rtol: {rtol}"
    
            if err_s == "warn":
                warnings.warn(err_msg, RuntimeWarning, stacklevel=2)
            elif err_s == "raise":
                raise FloatingPointError(err_msg)
            elif err_s == "print":
                print(err_msg)
    
        with errstate(invalid='ignore'):
    
>           result = (less_equal(abs(x - y), atol + rtol * abs(y))
                                     ^^^^^
                      & isfinite(y)
                      | (x == y))
E           ValueError: operands could not be broadcast together with shapes (664,2) (502,2)
/usr/lib/python3.12/site-packages/numpy/_core/numeric.py:2507: ValueError
________________________ PlyTest.test_vertex_attributes ________________________
[gw6] linux -- Python 3.12.12 /builds/strophy/aports/testing/py3-trimesh/src/trimesh-4.11.2/.testenv/bin/python3
self = <tests.test_ply.PlyTest testMethod=test_vertex_attributes>
    def test_vertex_attributes(self):
        """
        Test writing vertex attributes to a ply, by reading them back and asserting the
        written attributes array matches
        """
    
        m = g.get_mesh("box.STL")
        test_1d_attribute = g.np.copy(m.vertices[:, 0])
        test_nd_attribute = g.np.copy(m.vertices)
        m.vertex_attributes["test_1d_attribute"] = test_1d_attribute
        m.vertex_attributes["test_nd_attribute"] = test_nd_attribute
    
        export = m.export(file_type="ply")
        reconstructed = g.roundtrip(export, file_type="ply")
    
        vertex_attributes = reconstructed.metadata["_ply_raw"]["vertex"]["data"]
        result_1d = vertex_attributes["test_1d_attribute"]
        result_nd = vertex_attributes["test_nd_attribute"]["f1"]
    
>       g.np.testing.assert_almost_equal(result_1d, test_1d_attribute)
E       AssertionError: 
E       Arrays are not almost equal to 7 decimals
E       
E       Mismatched elements: 4455 / 4455 (100%)
E       First 5 mismatches are at indices:
E        [0]: 6.8986592767e-313 (ACTUAL), 6.4057698249816895 (DESIRED)
E        [1]: 6.8986592767e-313 (ACTUAL), 6.4057698249816895 (DESIRED)
E        [2]: 6.8986592767e-313 (ACTUAL), 6.4057698249816895 (DESIRED)
E        [3]: 6.8986592767e-313 (ACTUAL), 6.4057698249816895 (DESIRED)
E        [4]: 3.414826356378e-312 (ACTUAL), 6.4647698402404785 (DESIRED)
E       Max absolute difference among violations: 6.523769855499
E       Max relative difference among violations: 1.
E        ACTUAL: array([6.8986593e-313, 6.8986593e-313, 6.8986593e-313, ...,
E              1.2470836e-314, 1.2470836e-314, 6.8986593e-313],
E             shape=(4455,), dtype='<f8')
E        DESIRED: array([6.4057698, 6.4057698, 6.4057698, ..., 6.1128769, 6.1128769,
E              6.4057698], shape=(4455,))
tests/test_ply.py:106: AssertionError
_________________________ PlyTest.test_face_attributes _________________________
[gw4] linux -- Python 3.12.12 /builds/strophy/aports/testing/py3-trimesh/src/trimesh-4.11.2/.testenv/bin/python3
self = <tests.test_ply.PlyTest testMethod=test_face_attributes>
    def test_face_attributes(self):
        # Test writing face attributes to a ply, by reading
        # them back and asserting the written attributes array matches
    
        for encoding in ["binary", "ascii"]:
            for dt in [g.np.float32, g.np.float64]:
                m = g.get_mesh("box.STL")
                test_1d_attribute = g.np.copy(m.face_angles[:, 0])
                test_nd_attribute = g.np.copy(m.face_angles)
                m.face_attributes["test_1d_attribute"] = test_1d_attribute.astype(dt)
                m.face_attributes["test_nd_attribute"] = test_nd_attribute.astype(dt)
    
                export = m.export(
                    file_type="ply", include_attributes=True, encoding=encoding
                )
                reconstructed = g.roundtrip(export, file_type="ply", process=False)
    
                face_attributes = reconstructed.metadata["_ply_raw"]["face"]["data"]
                result_1d = face_attributes["test_1d_attribute"]
                if encoding == "binary":
                    # only binary format allows this
                    result_nd = face_attributes["test_nd_attribute"]["f1"]
                else:
                    result_nd = face_attributes["test_nd_attribute"]
    
>               g.np.testing.assert_almost_equal(result_1d, test_1d_attribute)
E               AssertionError: 
E               Arrays are not almost equal to 7 decimals
E               
E               nan location mismatch:
E                ACTUAL: array([-4.0472194e+16, -2.0552418e-08,  8.8456492e+04, ...,
E                      -2.9970339e+22, -4.0331457e+16, -4.0331457e+16],
E                     shape=(8954,), dtype='<f4')
E                DESIRED: array([1.5707963, 1.0913298, 1.5365074, ..., 2.3561945, 0.7853982,
E                      0.7853982], shape=(8954,))
tests/test_ply.py:134: AssertionError
____________________ GLTFTest.test_export_custom_attributes ____________________
[gw7] linux -- Python 3.12.12 /builds/strophy/aports/testing/py3-trimesh/src/trimesh-4.11.2/.testenv/bin/python3
self = <tests.test_gltf.GLTFTest testMethod=test_export_custom_attributes>
    def test_export_custom_attributes(self):
        # Write and read custom vertex attributes to gltf
        sphere = g.trimesh.primitives.Sphere()
        v_count, _ = sphere.vertices.shape
    
        sphere.vertex_attributes["_CustomFloat32Scalar"] = g.np.random.rand(
            v_count, 1
        ).astype(g.np.float32)
        sphere.vertex_attributes["_CustomFloat32Vec3"] = g.np.random.rand(
            v_count, 3
        ).astype(g.np.float32)
        sphere.vertex_attributes["_CustomFloat32Mat4"] = g.np.random.rand(
            v_count, 4, 4
        ).astype(g.np.float32)
    
        # export as GLB bytes
        export = sphere.export(file_type="glb")
        # this should validate just fine
        validate_glb(export)
    
        # uint32 is slightly off-label and may cause
        # validators to fail but if you're a bad larry who
        # doesn't follow the rules it should be fine
        sphere.vertex_attributes["_CustomUInt32Scalar"] = g.np.random.randint(
            0, 1000, size=(v_count, 1)
        ).astype(g.np.uint32)
    
        # when you add a uint16/int16 the gltf-validator
        # complains about the 4-byte boundaries even though
        # all their lengths and offsets mod 4 are zero
        # not sure if that's a validator bug or what
        sphere.vertex_attributes["_CustomUInt16Scalar"] = g.np.random.randint(
            0, 1000, size=(v_count, 1)
        ).astype(g.np.uint16)
        sphere.vertex_attributes["_CustomInt16Scalar"] = g.np.random.randint(
            0, 1000, size=(v_count, 1)
        ).astype(g.np.int16)
    
        # export as GLB then re-load
        export = sphere.export(file_type="glb")
    
        r = g.trimesh.load(g.trimesh.util.wrap_as_stream(export), file_type="glb")
    
        for _, val in r.geometry.items():
            assert set(val.vertex_attributes.keys()) == set(
                sphere.vertex_attributes.keys()
            )
            for key in val.vertex_attributes:
                # the vertex attribute before round-tripping
                ori = sphere.vertex_attributes[key]
    
                # non 4-byte aligned attributes would have been padded
                check = val.vertex_attributes[key][:, : ori.shape[1]]
    
>               assert g.np.allclose(ori, check), key
E               AssertionError: _CustomFloat32Scalar
E               assert False
E                +  where False = <function allclose at 0x3ff4c81e130>(array([[1.78557485e-01],\n       [7.64179289e-01],\n       [4.88104403e-01],\n       [2.13032410e-01],\n       [6.76045477e-01],\n       [1.72032222e-01],\n       [1.05292611e-01],\n       [2.76651591e-01],\n       [9.32412148e-02],\n       [7.08671927e-01],\n       [8.45383883e-01],\n       [1.59452073e-02],\n       [6.11236751e-01],\n       [1.41733764e-02],\n       [9.26103219e-02],\n       [8.46374512e-01],\n       [8.07372808e-01],\n       [9.98835623e-01],\n       [5.66610694e-01],\n       [3.46271455e-01],\n       [7.11632669e-01],\n       [4.38893676e-01],\n       [9.66912448e-01],\n       [2.80399650e-01],\n       [8.15345109e-01],\n       [5.29560089e-01],\n       [1.23304993e-01],\n       [3.16784412e-01],\n       [5.66678345e-01],\n       [7.68971503e-01],\n       [7.21466839e-01],\n       [8.83644596e-02],\n       [7.66119838e-01],\n       [6.53052032e-02],\n       [7.29488581e-02],\n       [6.81485474e-01],\n       [8.88428450e-01],\n       [1.64339975e-01],\n       [3.55258644e-01],\n       [1.83149964e-01],\n       [6.87364638e-01],\n       [1.81428939e-01],\n       [4.45469558e-01],\n       [6.06894433e-01],\n       [3.53891730e-01],\n       [7.51811802e-01],\n       [8.33294153e-01],\n       [6.00830078e-01]...02e-01],\n       [6.41341627e-01],\n       [5.26171684e-01],\n       [1.33700415e-01],\n       [8.70324314e-01],\n       [1.72386259e-01],\n       [8.53354275e-01],\n       [2.25214228e-01],\n       [1.62106991e-01],\n       [4.36152726e-01],\n       [3.61672640e-02],\n       [7.96875656e-01],\n       [6.67929173e-01],\n       [5.78643441e-01],\n       [7.40894735e-01],\n       [9.90205944e-01],\n       [2.56226361e-01],\n       [2.54459549e-02],\n       [5.68959832e-01],\n       [9.10290778e-01],\n       [7.77455688e-01],\n       [1.36427194e-01],\n       [8.35757494e-01],\n       [5.25348485e-01],\n       [2.21652582e-01],\n       [1.23829611e-01],\n       [3.85971099e-01],\n       [6.22173011e-01],\n       [7.30783820e-01],\n       [1.51006445e-01],\n       [6.43105805e-01],\n       [2.59271204e-01],\n       [4.73210156e-01],\n       [1.77116930e-01],\n       [2.27904990e-01],\n       [4.35855895e-01],\n       [5.64736485e-01],\n       [7.26099133e-01],\n       [3.84750277e-01],\n       [6.39364302e-01],\n       [3.96793246e-01],\n       [9.07050520e-02],\n       [6.05722189e-01],\n       [2.98411578e-01],\n       [2.82684505e-01],\n       [1.85717955e-01],\n       [1.68054730e-01],\n       [6.84710860e-01]], dtype=float32), array([[-2.75471211e+04],\n       [ 2.01578350e+01],\n       [-5.00307001e+11],\n       [ 5.87450119e-13],\n       [ 3.89705851e+10],\n       [ 1.00844172e-05],\n       [-4.54749501e-15],\n       [ 8.47624844e+04],\n       [ 7.67947245e+00],\n       [-4.42377066e-35],\n       [ 1.18818715e-26],\n       [-6.00005148e-35],\n       [ 7.35008219e-37],\n       [ 1.45310127e+31],\n       [ 1.77305735e+36],\n       [ 1.58273621e-38],\n       [-7.28191176e+36],\n       [-5.22405186e-09],\n       [ 1.66542952e+23],\n       [ 6.73560873e+37],\n       [-8.54000596e-30],\n       [-5.19764700e-12],\n       [-3.41963768e-27],\n       [-5.56823093e-32],\n       [ 4.72360742e+32],\n       [ 4.53213453e+00],\n       [ 4.67241953e+12],\n       [-1.40128328e-28],\n       [-9.96895359e+12],\n       [ 1.17717852e+11],\n       [ 1.09836377e-30],\n       [ 9.62127214e+27],\n       [ 1.24000265e+28],\n       [-2.32568923e-02],\n       [ 3.43595389e-06],\n       [-1.68486859e+13],\n       [ 1.85187933e-31],\n       [-4.88665029e-02],\n       [ 2.31937444e+33],\n       [-2.47324676e-13],\n       [ 1.67498770e-18],\n       [-2.94202142e-37],\n       [-1.20273653e-25],\n       [ 6.84291257e+28],\n       [ 4.65850800e+07],\n       [-6.01198636e-02],\n  ....30257394e-09],\n       [-4.64064392e+02],\n       [-5.18422092e-27],\n       [ 5.04760833e-35],\n       [ 4.74580801e+27],\n       [-1.56193851e-29],\n       [ 1.47081354e+20],\n       [ 1.57063673e+25],\n       [ 1.38980366e-19],\n       [ 2.47092645e-32],\n       [ 9.56430418e+24],\n       [-2.09092608e+35],\n       [ 8.76744922e+04],\n       [ 1.37958895e-17],\n       [ 3.65019635e-23],\n       [ 2.94683430e-33],\n       [ 2.35126417e+16],\n       [-3.66175805e+10],\n       [ 3.71850173e+13],\n       [-1.12992596e-27],\n       [ 4.55105663e-07],\n       [ 6.17735349e-02],\n       [-1.46619892e+23],\n       [ 3.57380791e+20],\n       [ 5.83230622e-38],\n       [-3.02310265e-03],\n       [-5.14281083e-16],\n       [ 9.97176229e+28],\n       [-6.54884652e-26],\n       [ 2.22954633e-08],\n       [-4.15547123e-23],\n       [ 4.81836743e-17],\n       [-4.32139920e+27],\n       [-3.10168951e-38],\n       [-9.21790034e-28],\n       [-6.10469839e-18],\n       [-4.21642038e+37],\n       [ 3.99228309e+20],\n       [ 3.50571245e+36],\n       [-1.23518949e-27],\n       [-4.76808180e-22],\n       [ 1.90400807e+24],\n       [ 7.09296850e-35],\n       [-3.03013325e+15],\n       [-7.23055832e-33],\n       [ 2.99788348e-06]], dtype='<f4'))
E                +    where <function allclose at 0x3ff4c81e130> = <module 'numpy' from '/usr/lib/python3.12/site-packages/numpy/__init__.py'>.allclose
E                +      where <module 'numpy' from '/usr/lib/python3.12/site-packages/numpy/__init__.py'> = g.np
tests/test_gltf.py:599: AssertionError

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions