From 1fbc12dfc43ab0753f269451810ed3d0bec8d196 Mon Sep 17 00:00:00 2001 From: Kenya Otsuka Date: Sat, 27 Jun 2026 06:27:47 +0000 Subject: [PATCH] chore(dataform): warn about future removal of MATLAB-compatible writes Add a FutureWarning to the MATLAB-compatible v7.3 .mat write paths that are slated to become bdpy-native plain HDF5: save_array (dense), save_multiarrays, SparseArray.save, and save_feature. This is warning-only. The on-disk format, the hdf5storage dependency, and the _mat_v73 reader are all unchanged, and reading existing hdf5storage / MATLAB v7.3 files remains supported. The actual switch to plain HDF5 is implemented on the refactor/drop-hdf5storage-write branch. --- bdpy/dataform/features.py | 12 +++++++ bdpy/dataform/sparse.py | 15 +++++++++ tests/dataform/test_features.py | 22 ++++++++++++- tests/dataform/test_sparse.py | 57 ++++++++++++++++++++++++++++++++- 4 files changed, 104 insertions(+), 2 deletions(-) diff --git a/bdpy/dataform/features.py b/bdpy/dataform/features.py index 193537f..f378538 100644 --- a/bdpy/dataform/features.py +++ b/bdpy/dataform/features.py @@ -22,6 +22,17 @@ from . import _mat_v73 +# Deprecation notice emitted by the MATLAB-compatible write path. The actual +# switch to bdpy-native plain HDF5 (and the drop of the hdf5storage write +# dependency) is implemented on the refactor/drop-hdf5storage-write branch. +_MATLAB_WRITE_FUTURE_WARNING = ( + "Writing MATLAB-compatible v7.3 .mat files is deprecated and will change " + "in a future release: bdpy will write bdpy-native plain HDF5 instead. " + "Newly written files will no longer be guaranteed to be readable by " + "MATLAB's load(). Reading existing hdf5storage / MATLAB v7.3 files remains " + "supported." +) + def _load_array_with_key(key: str, path: str) -> np.ndarray: # v5 .mat via scipy, v7.3 (HDF5) via h5py; avoids hdf5storage on the load @@ -535,6 +546,7 @@ def save_feature(feature: np.ndarray, base_dir: str, layer: str, label: str, ver print(f'{save_file} already exists. Skipped.') return None + warnings.warn(_MATLAB_WRITE_FUTURE_WARNING, FutureWarning, stacklevel=2) hdf5storage.savemat(save_file, {'feat': feature}) if verbose: print(f'Saved {save_file}.') diff --git a/bdpy/dataform/sparse.py b/bdpy/dataform/sparse.py index e22e0b3..716324f 100644 --- a/bdpy/dataform/sparse.py +++ b/bdpy/dataform/sparse.py @@ -7,6 +7,7 @@ import os +import warnings import h5py import hdf5storage @@ -20,6 +21,17 @@ # and still rely on hdf5storage for MATLAB-v7.3 metadata/compatibility behavior. _NUMPY2 = int(np.__version__.split('.')[0]) >= 2 +# Deprecation notice emitted by the MATLAB-compatible write paths. The actual +# switch to bdpy-native plain HDF5 (and the drop of the hdf5storage write +# dependency) is implemented on the refactor/drop-hdf5storage-write branch. +_MATLAB_WRITE_FUTURE_WARNING = ( + "Writing MATLAB-compatible v7.3 .mat files is deprecated and will change " + "in a future release: bdpy will write bdpy-native plain HDF5 instead. " + "Newly written files will no longer be guaranteed to be readable by " + "MATLAB's load(). Reading existing hdf5storage / MATLAB v7.3 files remains " + "supported." +) + def load_array(fname, key='data'): """Load an array (dense or sparse).""" @@ -46,6 +58,7 @@ def save_array(fname, array, key='data', dtype=np.float64, sparse=False): s_ary.save(fname, key=key, dtype=dtype) else: # Save as a dense array + warnings.warn(_MATLAB_WRITE_FUTURE_WARNING, FutureWarning, stacklevel=2) hdf5storage.savemat(fname, {key: array.astype(dtype)}, format='7.3', oned_as='column', @@ -56,6 +69,7 @@ def save_array(fname, array, key='data', dtype=np.float64, sparse=False): def save_multiarrays(fname, arrays): """Save arrays (dense).""" + warnings.warn(_MATLAB_WRITE_FUTURE_WARNING, FutureWarning, stacklevel=2) save_dict = {k: v for k, v in arrays.items()} hdf5storage.savemat(fname, save_dict, @@ -85,6 +99,7 @@ def dense(self): return self.__make_dense() def save(self, fname, key='data', dtype=np.float64): + warnings.warn(_MATLAB_WRITE_FUTURE_WARNING, FutureWarning, stacklevel=2) if _NUMPY2: # Avoid hdf5storage.savemat here (it can fail when overwriting an # existing sparse struct under NumPy 2.x) and write the struct with diff --git a/tests/dataform/test_features.py b/tests/dataform/test_features.py index d5d3a10..8b8bbd4 100644 --- a/tests/dataform/test_features.py +++ b/tests/dataform/test_features.py @@ -9,7 +9,8 @@ from numpy.testing import assert_array_equal import hdf5storage -from bdpy.dataform.features import Features +from bdpy.dataform import _mat_v73 +from bdpy.dataform.features import Features, save_feature def _prepare_mock_data( @@ -120,5 +121,24 @@ def test_features_get_label(self): self.alexnet_conv5_all[index, :] ) + +class TestSaveFeature(unittest.TestCase): + def test_save_feature_warns_future(self): + # save_feature writes a MATLAB-compatible v7.3 .mat file, a path that is + # deprecated (it becomes bdpy-native plain HDF5 in a future release); it + # must emit a FutureWarning while still writing a reloadable file. + feature = np.random.rand(1, 1000) + + with tempfile.TemporaryDirectory() as tmpdir: + with self.assertWarns(FutureWarning): + save_feature(feature, tmpdir, 'fc8', 'n01443537_22563') + + save_file = os.path.join(tmpdir, 'fc8', 'n01443537_22563.mat') + self.assertTrue(os.path.exists(save_file)) + + from_file = _mat_v73.loadmat_key(save_file, 'feat') + assert_array_equal(feature, from_file) + + if __name__ == "__main__": unittest.main() diff --git a/tests/dataform/test_sparse.py b/tests/dataform/test_sparse.py index 3047f6b..0895650 100644 --- a/tests/dataform/test_sparse.py +++ b/tests/dataform/test_sparse.py @@ -7,7 +7,9 @@ import h5py import numpy as np -from bdpy.dataform.sparse import load_array, save_array +from bdpy.dataform.sparse import ( + SparseArray, load_array, save_array, save_multiarrays +) class TestSparse(unittest.TestCase): @@ -81,6 +83,59 @@ def test_load_array_jl(self): os.path.join(data_dir, 'array_jl_sparse_v1.mat'), key='a') np.testing.assert_array_equal(data, testdata) + def test_save_array_dense_warns_future(self): + # The MATLAB-compatible dense write path is deprecated (it becomes + # bdpy-native plain HDF5 in a future release); it must emit a + # FutureWarning while still round-tripping unchanged. + with tempfile.TemporaryDirectory() as tmpdir: + fname = os.path.join(tmpdir, 'dense.mat') + original_data = np.random.rand(3, 2) + + with self.assertWarns(FutureWarning): + save_array(fname, original_data, key='testdata') + + from_file = load_array(fname, key='testdata') + np.testing.assert_array_equal(original_data, from_file) + + def test_save_array_sparse_warns_future(self): + with tempfile.TemporaryDirectory() as tmpdir: + fname = os.path.join(tmpdir, 'sparse.mat') + original_data = np.random.rand(3, 2) + original_data[original_data < 0.8] = 0 + + with self.assertWarns(FutureWarning): + save_array(fname, original_data, key='testdata', sparse=True) + + from_file = load_array(fname, key='testdata') + np.testing.assert_array_equal(original_data, from_file) + + def test_sparse_array_save_warns_future(self): + with tempfile.TemporaryDirectory() as tmpdir: + fname = os.path.join(tmpdir, 'sparse_direct.mat') + original_data = np.random.rand(3, 2) + original_data[original_data < 0.8] = 0 + + with self.assertWarns(FutureWarning): + SparseArray(original_data).save(fname, key='testdata') + + from_file = load_array(fname, key='testdata') + np.testing.assert_array_equal(original_data, from_file) + + def test_save_multiarrays_warns_future(self): + with tempfile.TemporaryDirectory() as tmpdir: + fname = os.path.join(tmpdir, 'multi.mat') + arrays = { + 'a': np.random.rand(3, 2), + 'b': np.random.rand(4,), + } + + with self.assertWarns(FutureWarning): + save_multiarrays(fname, arrays) + + for key, value in arrays.items(): + from_file = load_array(fname, key=key) + np.testing.assert_array_equal(value, from_file) + if __name__ == '__main__': unittest.main()