diff --git a/negpy/features/process/fade.py b/negpy/features/process/fade.py index ab05af34b..0b578bfb7 100644 --- a/negpy/features/process/fade.py +++ b/negpy/features/process/fade.py @@ -38,9 +38,9 @@ #: Percentile of the analysis grid that Remove Stain reads as each channel's white. WHITE_PCT = 99.9 #: To suggest Remove Stain, the brightest channel's white is within this density of full scale. -STAIN_FULL_SCALE_TOLERANCE = 0.1 -#: To suggest Remove Stain, blue is the darkest channel at the white, by at least this density. -STAIN_BLUE_MIN = 0.2 +STAIN_FULL_SCALE_TOLERANCE = 0.2 +#: To suggest Remove Stain, blue's white is at least this density below both red's and green's. +STAIN_BLUE_MIN = 0.08 #: Remove Stain puts the white at least this log density below full scale, so clear areas do not clip. FADE_WHITE_HEADROOM = 0.1 @@ -167,9 +167,9 @@ def white_anchored_grid(grid: ImageBuffer, white: tuple[float, float, float]) -> def stain_suggested(white: tuple[float, float, float]) -> bool: """Whether the white reads as a yellow stain on clear film: the brightest channel near full - scale, and blue clearly the darkest. A dim scan with no white object never qualifies.""" - brightest = max(white) - return brightest >= -STAIN_FULL_SCALE_TOLERANCE and white[2] <= min(white[0], white[1]) and brightest - white[2] >= STAIN_BLUE_MIN + scale, and blue below both red and green. A red cast leaves blue near green, and a dim scan + with no white object never qualifies.""" + return max(white) >= -STAIN_FULL_SCALE_TOLERANCE and min(white[0], white[1]) - white[2] >= STAIN_BLUE_MIN class EstimateMethod(StrEnum): diff --git a/tests/test_fade_white_anchor.py b/tests/test_fade_white_anchor.py index b8d88b30b..7843b0c97 100644 --- a/tests/test_fade_white_anchor.py +++ b/tests/test_fade_white_anchor.py @@ -249,10 +249,14 @@ def test_the_anchored_estimate_reads_the_true_survivals_through_a_stain(): [ ((-0.05, -0.18, -0.73), True), # NORAD 106-25: bright, heavy yellow stain ((-0.04, -0.16, -0.28), True), # NORAD 106-5: blue below red by more than the minimum + ((-0.14, -0.12, -0.49), True), # a heavy stain on a scan placed under full scale + ((-0.01, -0.04, -0.16), True), # a mild stain on a bright white ((-0.76, -1.05, -1.05), False), # SilverFast at gamma 2.2: no white near full scale ((-2.9, -3.4, -3.25), False), # a scan with no white object ((-0.25, -0.12, -0.01), False), # bluish whites: a snow or sky frame - ((0.0, -0.05, -0.15), False), # a stain under the minimum + ((0.0, -0.05, -0.10), False), # a stain under the minimum + ((-0.09, -0.02, -0.13), False), # a red cast: blue barely below red + ((-0.25, -0.30, -0.60), False), # a stain on a white too far under full scale ((0.0, -0.4, -0.3), False), # green, not blue, is darkest ], ) @@ -261,7 +265,7 @@ def test_stain_suggestion_rule(white, expected): def test_stain_suggestion_thresholds_are_the_documented_ones(): - assert (STAIN_FULL_SCALE_TOLERANCE, STAIN_BLUE_MIN) == (0.1, 0.2) + assert (STAIN_FULL_SCALE_TOLERANCE, STAIN_BLUE_MIN) == (0.2, 0.08) @unittest.skipUnless(GPUDevice.get().is_available, "GPU not available")