|
2 | 2 | import numpy as np |
3 | 3 |
|
4 | 4 | from napari_tmidas.processing_functions.skimage_filters import ( |
| 5 | + adaptive_threshold_bright, |
| 6 | + h_maxima_transform, |
5 | 7 | invert_image, |
| 8 | + percentile_threshold, |
| 9 | + rolling_ball_background, |
6 | 10 | simple_thresholding, |
| 11 | + white_tophat, |
7 | 12 | ) |
8 | 13 |
|
9 | 14 |
|
@@ -67,3 +72,101 @@ def test_simple_thresholding_different_thresholds(self): |
67 | 72 | assert ( |
68 | 73 | np.sum(result_high == 255) < np.prod(result_high.shape) * 0.3 |
69 | 74 | ) # Most pixels below 200 |
| 75 | + |
| 76 | + |
| 77 | +class TestBrightRegionExtraction: |
| 78 | + """Test suite for bright region extraction functions""" |
| 79 | + |
| 80 | + def test_white_tophat_basic(self): |
| 81 | + """Test white top-hat transform extracts bright features""" |
| 82 | + # Create image with bright spot on dark background |
| 83 | + image = np.zeros((100, 100), dtype=np.uint8) |
| 84 | + image[40:60, 40:60] = 200 # Bright square |
| 85 | + image[45:55, 45:55] = 255 # Brighter center |
| 86 | + |
| 87 | + result = white_tophat(image, footprint_size=15) |
| 88 | + |
| 89 | + # Result should have bright features extracted |
| 90 | + assert result.shape == image.shape |
| 91 | + assert result.max() > 0 # Should have some bright regions |
| 92 | + assert result.sum() < image.sum() # Background removed |
| 93 | + |
| 94 | + def test_percentile_threshold_original(self): |
| 95 | + """Test percentile thresholding with original values""" |
| 96 | + # Create image with gradient |
| 97 | + image = np.arange(0, 256, dtype=np.uint8).reshape(16, 16) |
| 98 | + |
| 99 | + result = percentile_threshold( |
| 100 | + image, percentile=90, output_type="original" |
| 101 | + ) |
| 102 | + |
| 103 | + # Only top 10% should remain |
| 104 | + assert result.shape == image.shape |
| 105 | + assert np.sum(result > 0) < image.size * 0.15 # Allow some margin |
| 106 | + assert result.max() == image.max() # Original max value preserved |
| 107 | + |
| 108 | + def test_percentile_threshold_binary(self): |
| 109 | + """Test percentile thresholding with binary output""" |
| 110 | + image = np.random.randint(0, 256, size=(50, 50), dtype=np.uint8) |
| 111 | + |
| 112 | + result = percentile_threshold( |
| 113 | + image, percentile=80, output_type="binary" |
| 114 | + ) |
| 115 | + |
| 116 | + # Should be binary |
| 117 | + assert result.dtype == np.uint8 |
| 118 | + assert set(np.unique(result)).issubset({0, 255}) |
| 119 | + |
| 120 | + def test_rolling_ball_background_subtraction(self): |
| 121 | + """Test rolling ball background subtraction""" |
| 122 | + # Create image with uneven background and bright spot |
| 123 | + x, y = np.meshgrid(np.arange(100), np.arange(100)) |
| 124 | + background = (50 + 30 * np.sin(x / 20) + 30 * np.sin(y / 20)).astype( |
| 125 | + np.uint8 |
| 126 | + ) |
| 127 | + image = background.copy() |
| 128 | + image[40:60, 40:60] += 150 # Add bright feature |
| 129 | + |
| 130 | + result = rolling_ball_background(image, radius=30) |
| 131 | + |
| 132 | + # Background should be reduced |
| 133 | + assert result.shape == image.shape |
| 134 | + # Center of bright spot should be brighter in result than in corners |
| 135 | + assert result[50, 50] > result[10, 10] |
| 136 | + |
| 137 | + def test_h_maxima_transform(self): |
| 138 | + """Test H-maxima transform suppresses small peaks""" |
| 139 | + # Create image with peaks of different heights |
| 140 | + image = np.zeros((100, 100), dtype=np.uint8) |
| 141 | + image[20, 20] = 100 # Small peak |
| 142 | + image[50, 50] = 200 # Large peak |
| 143 | + image[80, 80] = 80 # Very small peak |
| 144 | + |
| 145 | + result = h_maxima_transform(image, h=50.0) |
| 146 | + |
| 147 | + # Should suppress small peaks, keep large ones |
| 148 | + assert result.shape == image.shape |
| 149 | + # Large peak should remain prominent |
| 150 | + assert result[50, 50] > result[20, 20] |
| 151 | + |
| 152 | + def test_adaptive_threshold_bright(self): |
| 153 | + """Test adaptive thresholding with bright bias""" |
| 154 | + # Create image with varying brightness |
| 155 | + image = np.random.randint(0, 256, size=(100, 100), dtype=np.uint8) |
| 156 | + |
| 157 | + result = adaptive_threshold_bright(image, block_size=35, offset=-10.0) |
| 158 | + |
| 159 | + # Should be binary |
| 160 | + assert result.dtype == np.uint8 |
| 161 | + assert set(np.unique(result)).issubset({0, 255}) |
| 162 | + assert result.shape == image.shape |
| 163 | + |
| 164 | + def test_adaptive_threshold_even_blocksize(self): |
| 165 | + """Test that even block size is handled correctly""" |
| 166 | + image = np.random.randint(0, 256, size=(50, 50), dtype=np.uint8) |
| 167 | + |
| 168 | + # Should handle even block size by making it odd |
| 169 | + result = adaptive_threshold_bright(image, block_size=34, offset=0) |
| 170 | + |
| 171 | + assert result.shape == image.shape |
| 172 | + assert result.dtype == np.uint8 |
0 commit comments