|
30 | 30 |
|
31 | 31 | from nifreeze.data.filtering import ( |
32 | 32 | BVAL_ATOL, |
| 33 | + CLIPPING_DEGENERATE_RANGE_ERROR_MSG, |
| 34 | + CLIPPING_EMPTY_SELECTION_ERROR_MSG, |
| 35 | + CLIPPING_INVALID_THRESHOLDS_ERROR_MSG, |
| 36 | + CLIPPING_NONFINITE_THRESHOLDS_ERROR_MSG, |
| 37 | + ClippingValueError, |
33 | 38 | advanced_clip, |
34 | 39 | dwi_select_shells, |
35 | 40 | grand_mean_normalization, |
36 | 41 | robust_minmax_normalization, |
37 | 42 | ) |
38 | 43 |
|
39 | 44 |
|
| 45 | +def test_advanced_clip_empty_selection(): |
| 46 | + data = -np.ones((5, 5, 5), dtype=np.float32) |
| 47 | + |
| 48 | + with pytest.raises(ClippingValueError) as exc: |
| 49 | + advanced_clip(data, inplace=False, nonnegative=True) |
| 50 | + |
| 51 | + expected = CLIPPING_EMPTY_SELECTION_ERROR_MSG.format( |
| 52 | + constraints="nonnegative constraint and ", p_min=35.0, p_max=99.98 |
| 53 | + ) |
| 54 | + assert str(exc.value) == expected |
| 55 | + |
| 56 | + |
| 57 | +def test_advanced_clip_nonfinite_thresholds(monkeypatch): |
| 58 | + data = np.ones((5, 5, 5), dtype=np.float32) |
| 59 | + |
| 60 | + # Force non-finite thresholds regardless of input data |
| 61 | + def _percentile_returns_nan(*args, **kwargs): |
| 62 | + return np.nan |
| 63 | + |
| 64 | + from nifreeze.data import filtering |
| 65 | + |
| 66 | + monkeypatch.setattr(filtering.np, "percentile", _percentile_returns_nan) |
| 67 | + |
| 68 | + with pytest.raises(ClippingValueError) as exc: |
| 69 | + advanced_clip(data, inplace=False, nonnegative=True, p_min=35.0, p_max=99.98) |
| 70 | + |
| 71 | + expected = CLIPPING_NONFINITE_THRESHOLDS_ERROR_MSG.format( |
| 72 | + a_min=float("nan"), a_max=float("nan"), p_min=35.0, p_max=99.98, nonnegative=True |
| 73 | + ) |
| 74 | + assert str(exc.value) == expected |
| 75 | + |
| 76 | + |
| 77 | +def test_advanced_clip_invalid_thresholds(): |
| 78 | + data = np.arange(27, dtype=np.float32).reshape((3, 3, 3)) |
| 79 | + p = 50.0 |
| 80 | + |
| 81 | + with pytest.raises(ClippingValueError) as exc: |
| 82 | + advanced_clip(data, inplace=False, nonnegative=True, p_min=p, p_max=p) |
| 83 | + |
| 84 | + # With p_min == p_max, percentiles are equal (deterministically for this data) |
| 85 | + sel = data[np.isfinite(data)] |
| 86 | + a = float(np.percentile(sel, p)) |
| 87 | + expected = CLIPPING_INVALID_THRESHOLDS_ERROR_MSG.format( |
| 88 | + a_min=a, a_max=a, p_min=p, p_max=p, nonnegative=True |
| 89 | + ) |
| 90 | + assert str(exc.value) == expected |
| 91 | + |
| 92 | + |
| 93 | +def test_advanced_clip_degenerate_range(monkeypatch): |
| 94 | + data = np.arange(27, dtype=np.float32).reshape((3, 3, 3)) |
| 95 | + |
| 96 | + # Ensure thresholds are valid (a_max > a_min) and deterministic. |
| 97 | + # advanced_clip calls np.percentile twice (for a_min and a_max). |
| 98 | + percentiles = iter([0.0, 1.0]) |
| 99 | + |
| 100 | + def _percentile(_arr, _q): |
| 101 | + return next(percentiles) |
| 102 | + |
| 103 | + monkeypatch.setattr(np, "percentile", _percentile) |
| 104 | + |
| 105 | + # Force clipping to collapse all values to a constant after thresholds are deemed valid |
| 106 | + def _clip(_data, a_min=None, a_max=None, out=None): |
| 107 | + out.fill(0.5) |
| 108 | + return out |
| 109 | + |
| 110 | + monkeypatch.setattr(np, "clip", _clip) |
| 111 | + |
| 112 | + with pytest.raises(ClippingValueError) as exc: |
| 113 | + advanced_clip(data, inplace=False, nonnegative=True, p_min=35.0, p_max=99.98) |
| 114 | + |
| 115 | + # After our fake clip: data is constant 0.5; subtract min: all zeros; den == 0.0 |
| 116 | + expected = CLIPPING_DEGENERATE_RANGE_ERROR_MSG.format( |
| 117 | + den=0.0, a_min=0.0, a_max=1.0, nonnegative=True, data_finite=True, unique=1 |
| 118 | + ) |
| 119 | + assert str(exc.value) == expected |
| 120 | + |
| 121 | + |
40 | 122 | @pytest.mark.random_uniform_ndim_data((32, 32, 32), 0.0, 2.0) |
41 | 123 | @pytest.mark.parametrize( |
42 | 124 | "p_min, p_max, nonnegative, dtype, invert, inplace", |
|
0 commit comments