prpy 0.3.2__tar.gz → 0.3.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. {prpy-0.3.2/prpy.egg-info → prpy-0.3.3}/PKG-INFO +1 -1
  2. prpy-0.3.3/prpy/numpy/image_ops.cpython-310-darwin.so +0 -0
  3. {prpy-0.3.2 → prpy-0.3.3}/prpy/numpy/metric.py +71 -34
  4. {prpy-0.3.2 → prpy-0.3.3/prpy.egg-info}/PKG-INFO +1 -1
  5. {prpy-0.3.2 → prpy-0.3.3}/prpy.egg-info/SOURCES.txt +1 -0
  6. {prpy-0.3.2 → prpy-0.3.3}/tests/test_numpy_metric.py +44 -11
  7. {prpy-0.3.2 → prpy-0.3.3}/.gitignore +0 -0
  8. {prpy-0.3.2 → prpy-0.3.3}/LICENSE +0 -0
  9. {prpy-0.3.2 → prpy-0.3.3}/MANIFEST.in +0 -0
  10. {prpy-0.3.2 → prpy-0.3.3}/README.md +0 -0
  11. {prpy-0.3.2 → prpy-0.3.3}/prpy/__init__.py +0 -0
  12. {prpy-0.3.2 → prpy-0.3.3}/prpy/constants.py +0 -0
  13. {prpy-0.3.2 → prpy-0.3.3}/prpy/ffmpeg/__init__.py +0 -0
  14. {prpy-0.3.2 → prpy-0.3.3}/prpy/ffmpeg/probe.py +0 -0
  15. {prpy-0.3.2 → prpy-0.3.3}/prpy/ffmpeg/readwrite.py +0 -0
  16. {prpy-0.3.2 → prpy-0.3.3}/prpy/ffmpeg/utils.py +0 -0
  17. {prpy-0.3.2 → prpy-0.3.3}/prpy/helpers.py +0 -0
  18. {prpy-0.3.2 → prpy-0.3.3}/prpy/numpy/__init__.py +0 -0
  19. {prpy-0.3.2 → prpy-0.3.3}/prpy/numpy/core.py +0 -0
  20. {prpy-0.3.2 → prpy-0.3.3}/prpy/numpy/detect.py +0 -0
  21. {prpy-0.3.2 → prpy-0.3.3}/prpy/numpy/face.py +0 -0
  22. {prpy-0.3.2 → prpy-0.3.3}/prpy/numpy/filters.py +0 -0
  23. {prpy-0.3.2 → prpy-0.3.3}/prpy/numpy/freq.py +0 -0
  24. {prpy-0.3.2 → prpy-0.3.3}/prpy/numpy/image.py +0 -0
  25. {prpy-0.3.2 → prpy-0.3.3}/prpy/numpy/image_ops.c +0 -0
  26. {prpy-0.3.2 → prpy-0.3.3}/prpy/numpy/interp.py +0 -0
  27. {prpy-0.3.2 → prpy-0.3.3}/prpy/numpy/physio.py +0 -0
  28. {prpy-0.3.2 → prpy-0.3.3}/prpy/numpy/rolling.py +0 -0
  29. {prpy-0.3.2 → prpy-0.3.3}/prpy/numpy/stride_tricks.py +0 -0
  30. {prpy-0.3.2 → prpy-0.3.3}/prpy/numpy/utils.py +0 -0
  31. {prpy-0.3.2 → prpy-0.3.3}/prpy/tensorflow/__init__.py +0 -0
  32. {prpy-0.3.2 → prpy-0.3.3}/prpy/tensorflow/image.py +0 -0
  33. {prpy-0.3.2 → prpy-0.3.3}/prpy/tensorflow/loss.py +0 -0
  34. {prpy-0.3.2 → prpy-0.3.3}/prpy/tensorflow/lr_schedule.py +0 -0
  35. {prpy-0.3.2 → prpy-0.3.3}/prpy/tensorflow/model_saver.py +0 -0
  36. {prpy-0.3.2 → prpy-0.3.3}/prpy/tensorflow/nan.py +0 -0
  37. {prpy-0.3.2 → prpy-0.3.3}/prpy/tensorflow/optimizer.py +0 -0
  38. {prpy-0.3.2 → prpy-0.3.3}/prpy/tensorflow/signal.py +0 -0
  39. {prpy-0.3.2 → prpy-0.3.3}/prpy/torch/__init__.py +0 -0
  40. {prpy-0.3.2 → prpy-0.3.3}/prpy/torch/model_saver.py +0 -0
  41. {prpy-0.3.2 → prpy-0.3.3}/prpy.egg-info/dependency_links.txt +0 -0
  42. {prpy-0.3.2 → prpy-0.3.3}/prpy.egg-info/requires.txt +0 -0
  43. {prpy-0.3.2 → prpy-0.3.3}/prpy.egg-info/top_level.txt +0 -0
  44. {prpy-0.3.2 → prpy-0.3.3}/pyproject.toml +0 -0
  45. {prpy-0.3.2 → prpy-0.3.3}/setup.cfg +0 -0
  46. {prpy-0.3.2 → prpy-0.3.3}/setup.py +0 -0
  47. {prpy-0.3.2 → prpy-0.3.3}/tests/conftest.py +0 -0
  48. {prpy-0.3.2 → prpy-0.3.3}/tests/test_ffmpeg.py +0 -0
  49. {prpy-0.3.2 → prpy-0.3.3}/tests/test_numpy_core.py +0 -0
  50. {prpy-0.3.2 → prpy-0.3.3}/tests/test_numpy_detect.py +0 -0
  51. {prpy-0.3.2 → prpy-0.3.3}/tests/test_numpy_face.py +0 -0
  52. {prpy-0.3.2 → prpy-0.3.3}/tests/test_numpy_filters.py +0 -0
  53. {prpy-0.3.2 → prpy-0.3.3}/tests/test_numpy_freq.py +0 -0
  54. {prpy-0.3.2 → prpy-0.3.3}/tests/test_numpy_image.py +0 -0
  55. {prpy-0.3.2 → prpy-0.3.3}/tests/test_numpy_interp.py +0 -0
  56. {prpy-0.3.2 → prpy-0.3.3}/tests/test_numpy_physio.py +0 -0
  57. {prpy-0.3.2 → prpy-0.3.3}/tests/test_numpy_rolling.py +0 -0
  58. {prpy-0.3.2 → prpy-0.3.3}/tests/test_numpy_stride_tricks.py +0 -0
  59. {prpy-0.3.2 → prpy-0.3.3}/tests/test_numpy_utils.py +0 -0
  60. {prpy-0.3.2 → prpy-0.3.3}/tests/test_tensorflow.py +0 -0
  61. {prpy-0.3.2 → prpy-0.3.3}/tests/test_torch.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: prpy
3
- Version: 0.3.2
3
+ Version: 0.3.3
4
4
  Summary: Collection of Python utils for signal, image, and video processing
5
5
  Author-email: Philipp Rouast <philipp@rouast.com>
6
6
  License: MIT License
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2024 Philipp Rouast
1
+ # Copyright (c) 2025 Philipp Rouast
2
2
  #
3
3
  # Permission is hereby granted, free of charge, to any person obtaining a copy
4
4
  # of this software and associated documentation files (the "Software"), to deal
@@ -22,7 +22,15 @@ import numpy as np
22
22
  from scipy import signal
23
23
  from typing import Union
24
24
 
25
- def mag2db(mag: Union[np.ndarray, np.float64]) -> np.ndarray:
25
+ def _masked_sum(x, mask, axis, *, keepdims=False):
26
+ return np.where(mask, x, 0.0).sum(axis=axis, keepdims=keepdims)
27
+
28
+ def _safe_mean(x, mask, axis):
29
+ n_valid = mask.sum(axis=axis, keepdims=True)
30
+ n_safe = np.where(n_valid == 0, 1, n_valid)
31
+ return _masked_sum(x, mask, axis, keepdims=True) / n_safe
32
+
33
+ def _mag2db(mag: Union[np.ndarray, np.float64]) -> np.ndarray:
26
34
  """
27
35
  Magnitude to decibels element-wise.
28
36
 
@@ -37,7 +45,9 @@ def mag2db(mag: Union[np.ndarray, np.float64]) -> np.ndarray:
37
45
  def mae(
38
46
  y_true: np.ndarray,
39
47
  y_pred: np.ndarray,
40
- axis: Union[int, None] = -1
48
+ axis: Union[int, None] = -1,
49
+ *,
50
+ ignore_nan: bool = False
41
51
  ) -> np.ndarray:
42
52
  """
43
53
  Mean absolute error
@@ -46,6 +56,7 @@ def mae(
46
56
  y_true: True values. Shape (..., dim_n, dim_axis)
47
57
  y_pred: Predicted values. Shape (..., dim_n, dim_axis)
48
58
  axis: Axis along which the means are computed
59
+ ignore_nan: If true, metric is computed on non-NaN elements only.
49
60
  Returns:
50
61
  mae: The mean absolute error. Shape (..., dim_n)
51
62
  """
@@ -53,15 +64,23 @@ def mae(
53
64
  y_true = np.asarray(y_true)
54
65
  y_pred = np.asarray(y_pred)
55
66
  assert y_true.shape == y_pred.shape
56
- if np.all(np.isfinite(y_true)) and np.all(np.isfinite(y_pred)):
57
- return np.mean(np.abs(y_true - y_pred), axis=axis)
58
- else:
59
- return np.full(y_true.shape[:axis], fill_value=np.nan, dtype=y_true.dtype)
67
+ if not ignore_nan:
68
+ if np.all(np.isfinite(y_true)) and np.all(np.isfinite(y_pred)):
69
+ return np.mean(np.abs(y_true - y_pred), axis=axis)
70
+ else:
71
+ return np.full(y_true.shape[:axis], fill_value=np.nan, dtype=y_true.dtype)
72
+ mask = np.isfinite(y_true) & np.isfinite(y_pred)
73
+ err = np.abs(y_true - y_pred)
74
+ n_valid = mask.sum(axis=axis)
75
+ err_sum = _masked_sum(err, mask, axis)
76
+ return np.where(n_valid > 0, err_sum / n_valid, np.nan)
60
77
 
61
78
  def mse(
62
79
  y_true: np.ndarray,
63
80
  y_pred: np.ndarray,
64
- axis: Union[int, None] = -1
81
+ axis: Union[int, None] = -1,
82
+ *,
83
+ ignore_nan: bool = False
65
84
  ) -> np.ndarray:
66
85
  """
67
86
  Mean squared error
@@ -70,6 +89,7 @@ def mse(
70
89
  y_true: True values. Shape (..., dim_n, dim_axis)
71
90
  y_pred: Predicted values. Shape (..., dim_n, dim_axis)
72
91
  axis: Axis along which the means are computed
92
+ ignore_nan: If true, metric is computed on non-NaN elements only.
73
93
  Returns:
74
94
  mse: The mean squared error. Shape (..., dim_n)
75
95
  """
@@ -77,15 +97,23 @@ def mse(
77
97
  y_true = np.asarray(y_true)
78
98
  y_pred = np.asarray(y_pred)
79
99
  assert y_true.shape == y_pred.shape
80
- if np.all(np.isfinite(y_true)) and np.all(np.isfinite(y_pred)):
81
- return np.mean(np.square(y_true - y_pred), axis=axis)
82
- else:
83
- return np.full(y_true.shape[:axis], fill_value=np.nan, dtype=y_true.dtype)
100
+ if not ignore_nan:
101
+ if np.all(np.isfinite(y_true)) and np.all(np.isfinite(y_pred)):
102
+ return np.mean(np.square(y_true - y_pred), axis=axis)
103
+ else:
104
+ return np.full(y_true.shape[:axis], fill_value=np.nan, dtype=y_true.dtype)
105
+ mask = np.isfinite(y_true) & np.isfinite(y_pred)
106
+ sq = (y_true - y_pred) ** 2
107
+ n_valid = mask.sum(axis=axis)
108
+ sq_sum = _masked_sum(sq, mask, axis)
109
+ return np.where(n_valid > 0, sq_sum / n_valid, np.nan)
84
110
 
85
111
  def rmse(
86
112
  y_true: np.ndarray,
87
113
  y_pred: np.ndarray,
88
- axis: Union[int, None] = -1
114
+ axis: Union[int, None] = -1,
115
+ *,
116
+ ignore_nan: bool = False
89
117
  ) -> np.ndarray:
90
118
  """
91
119
  Root mean squared error
@@ -94,22 +122,18 @@ def rmse(
94
122
  y_true: True values. Shape (..., dim_n, dim_axis)
95
123
  y_pred: Predicted values. Shape (..., dim_n, dim_axis)
96
124
  axis: Axis along which the means are computed
125
+ ignore_nan: If true, metric is computed on non-NaN elements only.
97
126
  Returns:
98
127
  rmse: The root mean squared error. Shape (..., dim_n)
99
128
  """
100
- assert axis is None or isinstance(axis, int)
101
- y_true = np.asarray(y_true)
102
- y_pred = np.asarray(y_pred)
103
- assert y_true.shape == y_pred.shape
104
- if np.all(np.isfinite(y_true)) and np.all(np.isfinite(y_pred)):
105
- return np.sqrt(np.mean(np.square(y_true - y_pred), axis=axis))
106
- else:
107
- return np.full(y_true.shape[:axis], fill_value=np.nan, dtype=y_true.dtype)
129
+ return np.sqrt(mse(y_true, y_pred, axis=axis, ignore_nan=ignore_nan))
108
130
 
109
131
  def cor(
110
132
  y_true: np.ndarray,
111
133
  y_pred: np.ndarray,
112
- axis: Union[int, None] = -1
134
+ axis: Union[int, None] = -1,
135
+ *,
136
+ ignore_nan: bool = False,
113
137
  ) -> np.ndarray:
114
138
  """
115
139
  Pearson's correlation coefficient
@@ -118,6 +142,7 @@ def cor(
118
142
  y_true: True values. Shape (..., dim_n, dim_axis)
119
143
  y_pred: Predicted values. Shape (..., dim_n, dim_axis)
120
144
  axis: Axis along which correlations are computed
145
+ ignore_nan: If true, metric is computed on non-NaN elements only.
121
146
  Returns:
122
147
  cor: The correlation coefficients. Shape (..., dim_n)
123
148
  """
@@ -125,17 +150,29 @@ def cor(
125
150
  y_true = np.asarray(y_true)
126
151
  y_pred = np.asarray(y_pred)
127
152
  assert y_true.shape == y_pred.shape
128
- if np.all(np.isfinite(y_true)) and np.all(np.isfinite(y_pred)):
129
- res_true = y_true - np.mean(y_true, axis=axis, keepdims=True)
130
- res_pred = y_pred - np.mean(y_pred, axis=axis, keepdims=True)
131
- cov = np.mean(res_true * res_pred, axis=axis)
132
- var_true = np.mean(res_true**2, axis=axis)
133
- var_pred = np.mean(res_pred**2, axis=axis)
134
- sigma_true = np.sqrt(var_true)
135
- sigma_pred = np.sqrt(var_pred)
136
- return cov / (sigma_true * sigma_pred)
137
- else:
138
- return np.full(y_true.shape[:axis], fill_value=np.nan, dtype=y_true.dtype)
153
+ if not ignore_nan:
154
+ if np.all(np.isfinite(y_true)) and np.all(np.isfinite(y_pred)):
155
+ res_true = y_true - np.mean(y_true, axis=axis, keepdims=True)
156
+ res_pred = y_pred - np.mean(y_pred, axis=axis, keepdims=True)
157
+ cov = np.mean(res_true * res_pred, axis=axis)
158
+ var_true = np.mean(res_true**2, axis=axis)
159
+ var_pred = np.mean(res_pred**2, axis=axis)
160
+ sigma_true = np.sqrt(var_true)
161
+ sigma_pred = np.sqrt(var_pred)
162
+ return cov / (sigma_true * sigma_pred)
163
+ else:
164
+ return np.full(y_true.shape[:axis], fill_value=np.nan, dtype=y_true.dtype)
165
+ mask = np.isfinite(y_true) & np.isfinite(y_pred)
166
+ mean_t = _safe_mean(y_true, mask, axis)
167
+ mean_p = _safe_mean(y_pred, mask, axis)
168
+ r_true = np.where(mask, y_true - mean_t, 0.0)
169
+ r_pred = np.where(mask, y_pred - mean_p, 0.0)
170
+ n_valid = mask.sum(axis=axis)
171
+ cov = (r_true * r_pred).sum(axis=axis) / np.where(n_valid == 0, 1, n_valid)
172
+ var_t = (r_true ** 2).sum(axis=axis) / np.where(n_valid == 0, 1, n_valid)
173
+ var_p = (r_pred ** 2).sum(axis=axis) / np.where(n_valid == 0, 1, n_valid)
174
+ corr = cov / (np.sqrt(var_t) * np.sqrt(var_p))
175
+ return np.where(n_valid > 0, corr, np.nan)
139
176
 
140
177
  def snr(
141
178
  f_true: np.ndarray,
@@ -186,7 +223,7 @@ def snr(
186
223
  s_power = np.sum(pxx * gt_mask, axis=-1)
187
224
  f_mask = (f >= f_min) & (f <= f_max)
188
225
  all_power = np.sum(pxx * f_mask, axis=-1)
189
- snr = mag2db(s_power / (all_power - s_power))
226
+ snr = _mag2db(s_power / (all_power - s_power))
190
227
  return np.squeeze(snr)
191
228
  else:
192
229
  return np.full(y_pred.shape[:-1], fill_value=np.nan, dtype=y_pred.dtype)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: prpy
3
- Version: 0.3.2
3
+ Version: 0.3.3
4
4
  Summary: Collection of Python utils for signal, image, and video processing
5
5
  Author-email: Philipp Rouast <philipp@rouast.com>
6
6
  License: MIT License
@@ -24,6 +24,7 @@ prpy/numpy/filters.py
24
24
  prpy/numpy/freq.py
25
25
  prpy/numpy/image.py
26
26
  prpy/numpy/image_ops.c
27
+ prpy/numpy/image_ops.cpython-310-darwin.so
27
28
  prpy/numpy/interp.py
28
29
  prpy/numpy/metric.py
29
30
  prpy/numpy/physio.py
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2024 Philipp Rouast
1
+ # Copyright (c) 2025 Philipp Rouast
2
2
  #
3
3
  # Permission is hereby granted, free of charge, to any person obtaining a copy
4
4
  # of this software and associated documentation files (the "Software"), to deal
@@ -24,13 +24,31 @@ sys.path.append('../prpy')
24
24
  import numpy as np
25
25
  import pytest
26
26
 
27
- from prpy.numpy.metric import mag2db, mae, mse, rmse, cor, snr
27
+ from prpy.numpy.metric import _mag2db, mae, mse, rmse, cor, snr
28
+
29
+ def _make_pair(shape, seed=0, nan_ratio=0.25):
30
+ rng = np.random.default_rng(seed)
31
+ y_true = rng.normal(size=shape)
32
+ y_pred = rng.normal(size=shape)
33
+ nan_mask = rng.random(size=shape) < nan_ratio
34
+ y_true[nan_mask] = np.nan
35
+ y_pred[nan_mask & (rng.random(size=shape) < 0.5)] = np.nan
36
+ return y_true, y_pred
37
+
38
+ nanmean_abs = lambda a, b, ax: np.nanmean(np.abs(a - b), axis=ax)
39
+ nanmean_sq = lambda a, b, ax: np.nanmean((a - b) ** 2, axis=ax)
40
+
41
+ def nan_corr_1d(a, b):
42
+ m = np.isfinite(a) & np.isfinite(b)
43
+ if m.sum() < 2: return np.nan
44
+ a, b = a[m] - a[m].mean(), b[m] - b[m].mean()
45
+ return (a * b).mean() / (a.std() * b.std())
28
46
 
29
47
  @pytest.mark.parametrize("shape", [(3,), (2, 3), (2, 3, 5)])
30
48
  def test_mag2db(shape):
31
49
  x = np.zeros(shape=shape)
32
50
  x_copy = x.copy()
33
- out = mag2db(x)
51
+ out = _mag2db(x)
34
52
  assert out.shape == shape
35
53
  np.testing.assert_equal(x, x_copy)
36
54
 
@@ -45,14 +63,14 @@ def test_mae(shape):
45
63
  np.testing.assert_equal(y_true, y_true_copy)
46
64
  np.testing.assert_equal(y_pred, y_pred_copy)
47
65
 
48
- @pytest.mark.parametrize("shape", [(3,), (2, 3), (2, 3, 5)])
49
- def test_mse(shape):
50
- y_true = np.zeros(shape=shape)
51
- y_pred = np.zeros(shape=shape)
52
- y_true_copy = y_true.copy()
53
- y_pred_copy = y_pred.copy()
54
- out = mse(y_true=y_true, y_pred=y_pred, axis=-1)
55
- assert out.shape == shape[:-1]
66
+ @pytest.mark.parametrize("shape", [(4,), (2, 4), (2, 3, 4)])
67
+ def test_mae_ignore_nan(shape):
68
+ y_true, y_pred = _make_pair(shape)
69
+ y_true_copy, y_pred_copy = y_true.copy(), y_pred.copy()
70
+ legacy = mae(y_true, y_pred, axis=-1)
71
+ assert np.isnan(legacy).any()
72
+ ref = nanmean_abs(y_true, y_pred, -1)
73
+ np.testing.assert_allclose(mae(y_true, y_pred, ignore_nan=True), ref, equal_nan=True)
56
74
  np.testing.assert_equal(y_true, y_true_copy)
57
75
  np.testing.assert_equal(y_pred, y_pred_copy)
58
76
 
@@ -67,6 +85,13 @@ def test_rmse(shape):
67
85
  np.testing.assert_equal(y_true, y_true_copy)
68
86
  np.testing.assert_equal(y_pred, y_pred_copy)
69
87
 
88
+ @pytest.mark.parametrize("shape", [(4,), (2,4), (2,3,4)])
89
+ def test_mse_rmse_ignore_nan(shape):
90
+ y_t, y_p = _make_pair(shape, seed=1)
91
+ ref_mse = nanmean_sq(y_t, y_p, -1)
92
+ np.testing.assert_allclose(mse(y_t, y_p, ignore_nan=True), ref_mse, equal_nan=True)
93
+ np.testing.assert_allclose(rmse(y_t, y_p, ignore_nan=True), np.sqrt(ref_mse), equal_nan=True)
94
+
70
95
  @pytest.mark.parametrize("shape", [(3,), (2, 3), (2, 3, 5)])
71
96
  def test_cor(shape):
72
97
  y_true = np.random.uniform(size=shape)
@@ -80,6 +105,14 @@ def test_cor(shape):
80
105
  np.testing.assert_equal(y_true, y_true_copy)
81
106
  np.testing.assert_equal(y_pred, y_pred_copy)
82
107
 
108
+ @pytest.mark.parametrize("shape", [(5,), (3,5), (2,3,5)])
109
+ def test_cor_ignore_nan(shape):
110
+ y_t, y_p = _make_pair(shape, seed=2)
111
+ ref = np.array([nan_corr_1d(a, b)
112
+ for a, b in zip(y_t.reshape(-1, shape[-1]),
113
+ y_p.reshape(-1, shape[-1]))]).reshape(shape[:-1])
114
+ np.testing.assert_allclose(cor(y_t, y_p, ignore_nan=True), ref, equal_nan=True)
115
+
83
116
  @pytest.mark.parametrize("shape", [(6,), (10, 6)])
84
117
  @pytest.mark.parametrize("f_s", [5., 5])
85
118
  @pytest.mark.parametrize("tol", [.2, 1])
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes