Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions bdpy/dataform/features.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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}.')
Expand Down
15 changes: 15 additions & 0 deletions bdpy/dataform/sparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@


import os
import warnings

import h5py
import hdf5storage
Expand All @@ -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)."""
Expand All @@ -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',
Expand All @@ -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,
Expand Down Expand Up @@ -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
Expand Down
22 changes: 21 additions & 1 deletion tests/dataform/test_features.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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()
57 changes: 56 additions & 1 deletion tests/dataform/test_sparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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()
Loading