| """Regression guards for the brightness_range bug class. |
| |
| ImageDataGenerator(brightness_range=...) round-trips [0,1] float images through |
| PIL and returns all-black batches — it silently collapsed every early training |
| run. These tests pin the invariants: no brightness_range in the train |
| generator, and augmentation preserves non-zero [0,1] output. |
| """ |
| import numpy as np |
| import pytest |
|
|
| tf = pytest.importorskip("tensorflow") |
|
|
| from ml.utils.data_loader import CropDatasetLoader |
|
|
|
|
| def test_train_generator_source_has_no_brightness_range(): |
| import inspect |
| from ml.utils import data_loader |
| src = inspect.getsource(data_loader.CropDatasetLoader.create_data_generators) |
| for line in src.splitlines(): |
| stripped = line.strip() |
| if stripped.startswith("#") or "OMITTED" in stripped: |
| continue |
| assert "brightness_range=" not in stripped, ( |
| "brightness_range reintroduced into create_data_generators — it " |
| "destroys [0,1] float batches (all-black images). See data_loader.py " |
| "comments and ml/scripts/diagnose_pipeline.py." |
| ) |
|
|
|
|
| def test_random_augment_preserves_unit_range(): |
| loader = CropDatasetLoader.__new__(CropDatasetLoader) |
| rng = np.random.default_rng(0) |
| img = rng.uniform(0.2, 0.9, (224, 224, 3)).astype(np.float32) |
| np.random.seed(1) |
| out = loader._random_augment_image(img) |
| assert out.dtype == np.float32 |
| assert out.shape == img.shape |
| assert out.min() >= 0.0 and out.max() <= 1.0 |
| |
| assert out.max() > 1e-6 |
| assert out.mean() > 0.05 |
|
|
|
|
| def test_imagedatagen_flow_does_not_zero_batches(): |
| """End-to-end probe mirroring the in-pipeline AUG CHECK.""" |
| datagen = tf.keras.preprocessing.image.ImageDataGenerator( |
| rotation_range=30, |
| width_shift_range=0.2, |
| height_shift_range=0.2, |
| shear_range=0.2, |
| zoom_range=0.3, |
| horizontal_flip=True, |
| vertical_flip=True, |
| fill_mode="nearest", |
| ) |
| rng = np.random.default_rng(3) |
| X = rng.uniform(0.2, 0.9, (8, 64, 64, 3)).astype(np.float32) |
| y = np.eye(2, dtype=np.float32)[rng.integers(0, 2, 8)] |
| batch_x, _ = next(iter(datagen.flow(X, y, batch_size=8, shuffle=False))) |
| assert batch_x.max() > 1e-6, "augmented batch is all-zero (brightness bug class)" |
| assert batch_x.max() <= 2.0, "augmentation rescaled beyond [0,1]" |
|
|